317 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
logicog 434acbcc13 Merge pull request #397 from DrDoof/feat/stp-lag-entity
stp: treat a link aggregation group as one entity
2026-09-14 09:32:36 +02:00
René van Dorst 1aae2fac30 Merge pull request #364 from dw4rren/add-mokerlink-poe-2g080110gs
add support for MokerLink POE-2G080110GS
2026-09-14 09:23:15 +02:00
David Warren 1f9b3df3e6 Add basic support for MokerLink POE-2G080110GS 2026-09-13 23:18:55 -04: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
René van Dorst 5a2c4ca029 Merge pull request #421 from DrDoof/fix/fileadder-binary-safe-length
fileadder: track the data length instead of measuring it with strlen
2026-09-13 11:51:37 +00:00
René van Dorst 63e367d298 Merge pull request #424 from DrDoof/refactor/split-sfp-boot
Move the boot and SFP handling out of the common area into BANK3
2026-09-13 09:51:57 +00:00
René van Dorst 5e513daf17 Merge pull request #423 from DrDoof/fix/arp-refresh-from-ip
Refresh ARP entries from incoming IP traffic
2026-09-13 08:41:10 +00: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 b42eace21d Declare flash_default_config() in rtl837x_common.h
boot.c calls it from BANK3 and only rtlplayground.c had a prototype, so
the compiler saw an implicit declaration. The call itself was fine, the
function lives in the common area, but the warning was real. Move the
prototype into the shared header.

Reported by vDorst.
2026-09-13 05:18:07 +02:00
d00f 3dfd6a88c7 Move the SFP handling out of the common area into BANK3
sfp.c takes handle_sfp, sfp_module_read, sfp_apply_quirks, sfp_get_info,
sfp_read_field, sfp_print_info, setup_sfp_gpio, the quirk table and the module
state that goes with them. Everything exported is __banked: handle_sfp() and
sfp_print_info() are reached from cmd_parser.c in BANK2, the rest from the
common area, and both are bank boundaries.

The quirk table needed checking rather than assuming. Its entries are __code
pointers, and a __code pointer carries no bank, so passing one to a function in
a different bank hands it an offset read through the wrong window. That is safe
here because strcmp() sits in the common segment at 0x51f and is reached with a
plain lcall, so the bank register still says 3 while it runs. Verified in the
built image: the table at 0x35580 holds 0x563c and 0x5644, which resolve to
"QSFPTEK" and "QT-SFP+-T" inside BANK3.

Common segment 13393 -> 8432 bytes, leaving 7492 free rather than 2531. BANK3
holds 5710, BANK1 is unchanged and BANK2 grows by 31 for the trampolines.
All 34 machines in machine.h build and link, and the same walk over the
generated assembly finds no plain lcall crossing a bank boundary.
2026-09-13 05:16:50 +02:00
d00f 7fa26e0ae3 Move the boot and SerDes setup out of the common area into BANK3
The common segment is the scarce one: sixteen kilobytes shared by every bank,
and before this it had about 2.5 kB left. Most of what moves here runs once at
startup and does not have to sit in it. The two SerDes helpers are the
exception: the link-change handler and the SFP polling both reach sds_config()
while the switch is running, so they are not startup-only. None of it is
reached from an interrupt.

boot.c takes setup_i2c, init_smi, nic_setup, rtl8373_revision, rtl8224_enable,
early_boot_handle_button, set_hostname_default and the two SerDes helpers.
BANK3 is enabled with the linker flag; nothing else in the build needed
changing.

Everything moved is declared __banked, since every caller is outside BANK3:
main() and friends run from the common area and rtl837x_init.c calls
sds_config() and sds_config_mac() from BANK2. The calls the moved code makes
outward were already banked, so that side needed no work.

Common segment 13393 -> 10137 bytes, leaving 5787 free rather than 2531.
BANK3 holds 3732. All 34 machines in machine.h build and link. Walking
the generated assembly for plain lcalls that cross a bank boundary finds none
among 2808 calls; those build cleanly and only fail at run time, so it seemed
worth checking by machine rather than by eye.
2026-09-13 05:16:50 +02:00
d00f 1c8cdab4bf arp: take the sender MAC from where a received frame keeps it
IPBUF is laid out for frames we send, with the 12 byte TX descriptor in
front of the Ethernet header. A received frame starts with the Ethernet
header at uip_buf[0] and carries the RTL and VLAN tags after the
addresses, so IPBUF->ethhdr.src read the port mask and both VLAN fields
as a MAC. Every IP packet from the local network then replaced the
sender's ARP entry with that, and our replies went to an address nobody
has. The IP source address sits at the same offset in both layouts,
which is why the rest looked right.

Use BUF, the receive view uip_arp_arpin() already works from.
2026-09-13 04:14:21 +02:00
René van Dorst f49ca0d6fe Merge pull request #366 from gnacho/fix/kp9000-v31-mac-offset
Add factory MAC flash offset for keepLink KP-9000-9XHML-X V3.1 + mac command
2026-09-12 07:58:54 +00:00
gnacho 12fb6fd809 cmd_parser: parse mac into a global result buffer
parse_mac() now writes to the global mac_parse_result[6] and takes only the
index, like the other parsers (ip[4]). This drops the local __xdata buffer
and the now-unused hexval() helper.

Build (KP_9000_9XHML_X_V3_1): ROM 110302 -> 110193 bytes, XSEG and internal
RAM unchanged.
2026-09-11 23:42:01 +02:00
René van Dorst dad65fd550 Merge pull request #426 from DrDoof/fix/igmp-port-shift
igmp: mask the port number before shifting by it
2026-09-11 20:22:34 +00:00
René van Dorst dc82e044bc Merge pull request #436 from mintsoft/fix_gh_build
Fix Github Build - Add zlib
2026-09-11 17:22:37 +00:00
Rob Emery 7daa9effe0 Fixing dependencies and also broken link 2026-09-11 11:21:05 +00:00
Rob Emery 950586135a Build seems to be breaking on zlib.h being missing? 2026-09-11 11:14:32 +00:00
d00f c1ad800fc5 stp: name the MSTP state bits and the entity index
The state literals were spread over eighteen places in the file, sixteen
of them older than this branch, so naming only the new ones would have
left it written half one way and half the other.

Naming them separates two roles the literal hid: 0b11 is the forwarding
state in a call and a two bit field mask in a read-modify-write, hence
both STP_ST_FORWARDING and STP_ST_MASK. The comment above stp_state_set()
said what the defines now say, so it goes.

stp_parse() holds an entity index, not a port. cmd_parse_port() has
already mapped the front panel digit through machine.phys_to_log_port[],
and the aggregates continue that same logical range above the CPU port,
so call the variable ent. Reading the two branches of the if no longer
means following the parser two functions down.

No change to the generated code: 5331 instructions before and after.
2026-09-10 15:17:53 +02:00
gnacho dc4fb5becc fix(parser): reject malformed MAC inputs and make print helpers banked
parse_mac now accepts only a strict aa:bb:cc:dd:ee:ff (or '-' separators)
form: each octet is exactly two hex digits, the 5 separators are a single
consistent character, and nothing is allowed after the 6th octet. This
rejects the malformed values the reviewer flagged (repeated separators,
missing digits, trailing characters). A new inline hexval() helper does the
digit-to-value conversion and can be reused by future commands.

print_mac and print_ip are marked __banked to match cmd_parser(), which
lives in codeseg BANK2 and is itself __banked, so callers across the code
bank work correctly. cmd_parser.h is updated with the new prototypes and
the __banked qualifier on print_ip.

Verified with make MACHINE=KP_9000_9XHML_X_V3_1: the build links cleanly
and the internal-RAM (DSEG) layout is unchanged against main; XSEG is
unchanged and ROM grows by 184 bytes. A host-side test confirms parse_mac
rejects the two malformed inputs from the review.
2026-09-10 00:14:58 +02:00
d00f 78ecef15e9 stp gui: address a link aggregation group as one entity
The web UI became one index.html plus one main.js while this branch was
open, so the STP page changes had no html/stp.js and html/config.js left to
live in. They are re-applied here against the new structure.

A row now carries two derived fields: the name the CLI expects for it
("port N" or "lag N") and the key its controls are keyed by (the port
number or "LN"). A group's row is labelled with its front panel members,
which /stp.json reports in the mbr field.

conf_cmds and conf_overwrite accept the "stp lag N ..." forms so the
startup config keeps them.
2026-09-09 21:01:15 +02:00
d00f eab927f52a stp: name the port count instead of spelling out ten
Review of #397 pointed at the bare 10, which shows up in fifteen places. It
is the width of the port field in the MSTP register, so it is the CPU port
index plus one and it gets a name.

The web page counted to ten as well when listing a group's members. It uses
numPorts now, like the rest of that page does.

The aggregation section of the doc no longer names the OEM firmware or calls
a group a trunk, and it says plainly that the hardware keeps no per-group
state, so the firmware writes the members itself.

Generated code is unchanged: the only difference in the assembly is the
version string.
2026-09-09 20:58:35 +02:00
d00f 02c816647c stp: treat a link aggregation group as one entity
Spanning tree so far saw only physical ports. Two ports aggregated towards
the same neighbour look to the protocol like two parallel paths, so it
blocks one of them and cuts the group in half.

Give each of the four groups an STP entity of its own, numbered above the
physical ports. A member port answers to its group: BPDUs arriving on it
are accounted to the entity, the entity's state is written to every member
together, and its BPDUs carry the state of a live member. Ports outside
any group keep behaving exactly as before.

Membership comes from the aggregation tables once a second; when it
changed, the entity's state is reapplied to the new set of members. The
CLI takes lag <1-4> wherever it takes port <n>, the web UI shows a group
as one row, and the saved config keeps the lag form of the commands.
2026-09-09 20:58:35 +02:00
logicog d3f1069285 Merge pull request #315 from eraiza0816/feat/webui-compression
Feat/webui: compress the embedded assets and switch to a single-page layout
2026-09-09 15:31:22 +02:00
eraiza0816 98bda62181 Fix send_counters validation and changelog 2026-09-09 17:51:51 +09:00
René van Dorst 3f65db9984 Merge pull request #413 from DrDoof/fix/vlan-name-bounds
vlan: bound the name table before writing into it
2026-09-09 08:47:05 +02:00
logicog df95c544d4 Merge pull request #418 from DrDoof/fix/crc16-psbank
crc16: leave the code bank as it was found
2026-09-09 06:13:56 +02:00
eraiza0816 7df3c87892 Fix counters rejection of logical port 0 2026-09-09 10:58:16 +09:00
d00f 53f12a42c5 crc16: banked wrapper for cross-bank callers
The lookup tables live in BANK1, so the routine switches the code bank to
reach them with movc and never switches it back. That is the contract the
__naked declaration already implied: same bank or common segment only.
The name now says it.

crc16_bank1 is the routine unchanged. crc16 becomes a __banked wrapper in
BANK1, so a caller from another bank has its own bank put back by
__sdcc_banked_ret. The two existing call sites take the direct path and
pay nothing.
2026-09-09 01:52:12 +02:00
d00f 1b514b5ea8 vlan: bound the name table before writing into it
A VLAN name is copied into vlan_names with nothing checking vlan_ptr
against VLAN_NAMES_SIZE. The only limit is the 128 byte command line, so
enough named VLANs walk straight off the end of a 1 kB table.

What sits immediately behind it makes this worse than a plain overflow: at
0x048A comes vlan_ptr itself, the index driving the write, then port_names
and cmd_buffer. Once the index is corrupted the following writes land
wherever it now points, and the configuration is replayed at boot, so a
config file that overflows does it again on every start.

Measure the name first and refuse it if the three id digits, the name, its
separating space and the terminator would not fit. The check goes before
vlan_name_remove() so a name that does not fit cannot take the existing one
with it, and the VLAN still gets its ports: dropping the whole line would
lose port membership during a config replay, which is worse than losing a
name.

The copy walks a pointer instead of indexing vlan_names[vlan_ptr++] each time,
and the word offset comes out of cmd_words_b once. Writing the bound as
VLAN_NAMES_SIZE - 5 folds the constant at compile time instead of adding it on
every call. BANK2 ends up 25 bytes smaller than it was before the check
existed.
2026-09-09 00:38:37 +02:00
René van Dorst dd09be9dd0 Merge pull request #412 from DrDoof/fix/httpd-init-conn
httpd: do not initialise tstate through uip_conn
2026-09-08 22:22:08 +00:00
René van Dorst b2b7f8a0c6 Merge pull request #414 from DrDoof/fix/dhcp-restart-leak
dhcp: do not leave the old socket behind when starting again
2026-09-08 22:19:18 +00:00
René van Dorst 5b28ddf80a Merge pull request #416 from DrDoof/fix/phy-100m-half-adv
phy: advertise half duplex only when half duplex was asked for
2026-09-08 22:06:11 +00:00
d00f 624f93a28a igmp: mask the port number before shifting by it
The RTL tag carries a 4-bit port number for received frames, and the two
places that turn it into a mask bit take the whole byte:

	entry.pmask |= (1L << (IGMP_I->rtl_tag.pmask >> 8));

The comment beside it already says only the low four bits count, but nothing
takes them. rtl837x_stp.c:424 does the same extraction and does mask:

	stp_scratch = ((uint8_t)HTONS(STP_I->rtl_tag.pmask)) & 0x0f;

With a count above 15 the result is not a bit at all. SDCC narrows the 1L to
16 bits and emits the shift as a djnz loop, so a count of 200 spins the loop
200 times and leaves zero behind: the join adds no port and the leave clears
none, neither saying anything. It also spends those iterations inside packet
handling.

Mask to four bits and shift a uint16_t, which is the width the surrounding
code works in. Six bytes of BANK1.

Worth saying that this does not make snooping work on its own. struct
igmp_pkt has no vlan_tag field while struct eth_in describes the same frame
with one, so every IP and IGMP field below rtl_tag is read four bytes early.
2026-09-08 23:51:25 +02:00
d00f fe3da0c7a5 phy: advertise half duplex only when half duplex was asked for
The 100M branch of phy_set_speed() was missing an else. A port set to 100M
half wrote 0x1481, walked into the next if, found duplex was not 1, took its
else and overwrote the register with 0x1581 straight away, so it advertised
100BASE-TX half and full both. That is what asking for both is meant to do,
and forcing half is something people do to match a partner that cannot
negotiate, so the link comes up looking fine and misbehaves under load.

Bit 7 is 100BASE-TX half and bit 8 is full, which makes 0x1481, 0x1501 and
0x1581 half, full and both, the same shape as 0x1421, 0x1441 and 0x1461 in
the 10M branch above.

Both branches now choose the advertisement into a variable and write it once,
and they compare against the PHY_DUPLEX_* names instead of 0, 1 and whatever
is left. Same values reach the PHY; BANK2 loses 92 bytes.
2026-09-08 23:49:11 +02:00
d00f 412098d0ed phy: advertise half duplex only when half duplex was asked for
The 100M branch of phy_set_speed() is missing an else. A port set to 100M
half writes 0x1481, falls into the next if, finds duplex is not 1 and takes
its else, and immediately overwrites the register with 0x1581. The port
ends up advertising 100BASE-TX half and full, which is what "both" is
supposed to mean.

The 10M branch directly above has the chain the 100M one meant to have, and
the values line up with it: bit 7 is 100BASE-TX half, bit 8 is full, so
0x1481, 0x1501 and 0x1581 are half, full and both, exactly as 0x1421,
0x1441 and 0x1461 are for 10M.

Forcing half duplex is something people do to match a link partner that
cannot negotiate, so quietly advertising full as well is the wrong way for
this to fail.

Not tried on hardware: every port on the switch here has a link up, and
setting the speed restarts negotiation.
2026-09-08 23:49:11 +02:00
René van Dorst 5ecce86ef1 Merge pull request #422 from DrDoof/fix/no-idle-in-isr
Do not enter Idle mode from the external interrupt 2 handler
2026-09-08 21:47:02 +00:00
d00f 247b230212 Do not enter Idle mode from the external interrupt 2 handler
isr_ext2() ends with PCON |= 1, putting the core to sleep from inside an
interrupt handler.

The DW8051 leaves idle when any enabled interrupt is activated, so the core
does not stay down for long. It naps until the next enabled interrupt request
turns up, with the 200 Hz system tick putting a ceiling of 5 ms on that, and
then carries on.

The sleep buys nothing either way, because the instruction after it is the
return. While it lasts the handler holds the priority latch, so everything at
its level or below waits, and that includes the tick, the console and the NIC
receive interrupt.

The handler still clears its flag, which is what it has to do. The bit numbers
beside the two masks were wrong: 0xef clears bit 4 and 0xdf clears bit 5,
not 7 and 6.
2026-09-08 23:35:27 +02:00
d00f c012dda029 Do not enter Idle mode from the external interrupt 2 handler
isr_ext2() ends with PCON |= 1, putting the core to sleep from inside an
interrupt handler.

The DW8051 leaves idle when any enabled interrupt is activated, so the core
does not stay down for long. It naps until the next enabled interrupt request
turns up, with the 200 Hz system tick putting a ceiling of 5 ms on that, and
then carries on.

The sleep buys nothing either way, because the instruction after it is the
return. While it lasts the handler holds the priority latch, so everything at
its level or below waits, and that includes the tick, the console and the NIC
receive interrupt.

The handler still clears its flag, which is what it has to do. The two wrong
comments next to the masks go as well: 0xef clears bit 4 and 0xdf clears bit 5,
not 7 and 6, and the masks already say that without help.
2026-09-08 23:01:28 +02:00
gnacho 6616a0c9ce feat(cli): add mac command to set the management MAC from the config
Add a `mac <aa:bb:cc:dd:ee:ff>` console command (and a bare `mac` to show
the current address) that overrides the management MAC at runtime. The
address is validated (unicast, globally administered, non-zero OUI) and
the static L2 management entry is re-registered so the change applies
without a reboot. Because it is a normal command, a line in the startup
config persists it across boots: execute_config() runs it before the final
static L2 entry and the derived hostname are set up.

Also point the keepLink KP-9000-9XHML-X V3.1 machine definition at the
factory MAC area (0x1FC000, same location other machines use) so a valid
factory MAC is picked up at boot instead of falling back to a locally
administered address.

The MAC parsing/printing lives in parse_mac()/print_mac() helpers next to
parse_ip()/print_ip() so future commands can reuse them, and a memcmp()
helper joins the project's own memcpy()/memset() (no libc is linked).
2026-09-08 22:46:14 +02:00
d00f eab8c901cf Fix the EXIF bit numbers in the ext2 and ext3 handler comments
0xef clears bit 4 (IE2) and 0xdf clears bit 5 (IE3). The comments
said 7 and 6.
2026-09-08 22:43:06 +02:00
d00f 21a9fdf45a Do not enter Idle mode from the external interrupt 2 handler
isr_ext2() ends with PCON |= 1. On the DW8051 any enabled interrupt request
ends idle, so the handler sleeps until the next request arrives, the system
tick at the latest, and only then returns. Everything at the same priority
waits with it, and the instruction after the idle is RETI, so the sleep buys
nothing.

The handler still clears its flag, which is what it has to do. The other four
uses of PCON |= 1 are in task context and are left alone.
2026-09-08 22:43:05 +02:00
René van Dorst 1ed12d1101 Merge pull request #417 from DrDoof/fix/dhcp-missing-t1
dhcp: renew at half the lease when the server omits T1
2026-09-08 19:50:39 +00: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
eraiza0816 af4101f060 Fix language select, stat port index, devWalk polling, and duplicate BW command 2026-09-08 14:15:44 +09:00
d00f d6d9c368e9 Refresh ARP entries from incoming IP traffic
uip_arp_ipin() is an empty macro with its body under #if 0, both inherited
from the uIP import and untouched since. That leaves uip_arp_update()
reachable only from uip_arp_arpin(), so an entry is refreshed only by ARP
addressed to us and ages out after UIP_ARP_MAXAGE no matter how much traffic
passed over it.

When it goes, the next transmission to that host hits the miss path of
uip_arp_out(), which overwrites the packet in uip_buf with an ARP request and
returns. For TCP that costs a retransmission. For UDP nothing retransmits, and
handle_tx() sends the UDP connections through uip_arp_out() too, so the
datagram is lost.

The body could not be enabled as written: uip_len -= sizeof(struct uip_eth_hdr)
is a leftover from when UIP_LLH_LEN was 0. Here it is ETHER_HEADER_SIZE +
RTL_FRAME_DESC_SIZE and uip.c reads the IP header at uip_buf[UIP_LLH_LEN], so
uip_len has to keep the link layer in it. The rest of the function is sound.
2026-09-08 01:55:37 +02:00
d00f 36de9791be fileadder: track the data length instead of measuring it with strlen
replaceCalls() ended with strlen(&buffer[pos]), so the length recorded for a
file was the distance to its first 0x00 byte rather than the file size. The
byte just past the content happens to be zero today, which is the only reason
the numbers come out right.

Nothing in html/ contains a 0x00 today, so this is latent, but it is a trap
waiting for the first gzipped or binary asset: gzip puts a zero in the fourth
byte of its header. A gzipped style.css lands in the index as 3 bytes, and
since addr advances by that same value the next file is placed 3 bytes in and
overwrites what came before it. Two gzipped files in html/ are enough to see
it: both come out as 3 bytes and the second sits 1356 bytes inside the first.

Pass the length in and return the adjusted one. The substitution shortens
#{function} to #{NNN}, so the delta is known without measuring. The loops are
bounded by the length as well, which also covers -n, where nothing had put a
terminator in the buffer for strlen to find in the first place.

No output change: the image built from an unmodified html/ is byte for byte
the same before and after, and html_data.h is identical.
2026-09-08 00:52:24 +02:00
d00f eab17f8a3f dhcp: renew at half the lease when the server omits T1
Option 58 is optional, and dhcp_state.renewal is only ever written by
parse_opts(), so a server that leaves it out keeps it at zero. The ACK
handler then sets dhcp_timer to zero, and the callback treats a zero timer
in LEASING as time to renew, so it sends a REQUEST on the next pass.

That REQUEST does set a 30 second timeout, but the ACK that answers it
comes back through the same path and zeroes the timer again, so the two
sides trade REQUEST and ACK as fast as the round trip allows, with the
console printing the whole lease each time round.

RFC 2131 has the client renew at half the lease when the server does not
say, so use that, and fall back to a fixed value if there is no lease
either. Both only fill in a value that is missing: a server that asks for a
short renewal still gets exactly what it asked for, which a blanket lower
bound would have overridden.

Not reproduced here, that needs a server configured without the option.
2026-09-07 23:10:29 +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
d00f 4bcbada764 dhcp: do not leave the old socket behind when starting again
dhcp_start() never looked at whether DHCP was already running, and "ip dhcp"
calls it without stopping first, so a second start took a fresh slot from
uip_udp_conns and left the previous one bound.

The leak is the smaller half of it. dhcp_callback() decides whether a call
is for it by comparing the local port, not the connection, and every
leftover socket still holds port 68, so each one runs the state machine on
every poll. The timers live in one shared dhcp_state, so ticks count down
once per socket: two starts and the retransmit and renewal timing runs at
twice the intended rate.

Stop the old one first. dhcp_stop() already does exactly that, and the
state test keeps it away from a connection that was never created.
2026-09-07 22:44:51 +02:00
d00f 9fae0f9249 httpd: do not initialise tstate through uip_conn
uip_init() does not set uip_conn, and httpd_init() runs right after it, so
the assignment went through a pointer nothing had assigned yet.

It has stayed invisible because the startup code does clear xdata: uip_conn
is NULL rather than garbage, appstate sits 28 bytes into struct uip_conn, and
the byte therefore lands at xdata 0x1C. With the current map that is inside
the command line buffer, at an offset nothing reads while the line is empty,
and cmd_editor_init() runs later anyway.

Nothing is broken today, but it is a write to an address that has nothing to
do with the state being set, and it moves the moment the struct grows or the
map shifts. Address the connection directly.
2026-09-07 22:23:12 +02:00
eraiza0816 774e04816e Fix login loop and system input wipe 2026-09-08 00:31:14 +09:00
eraiza0816 ead1c40aac Merge upstream/main into feat/webui-compression 2026-09-07 15:36:46 +09:00
eraiza0816 d245249385 Merge upstream/main into feat/webui-compression
Resolve conflicts against ~55 upstream commits since 37eefb4:
- Makefile: keep upstream parallel-build fixes (machine_init.c, SUBDIRSCLEAN, html_data grouped target) and retain minify+gzip pipeline (-z, html_min, .css/.ico)
- tools: retain gzip support (zlib, -lz) lost in upstream revert
- httpd/httpd.c: keep Content-Encoding: gzip alongside upstream GET walk, config upload, syslog/hostname changes
- html: port upstream HTML/JS changes into SPA (single-page) bundle:
  * i18n: add port_col_devices / port_devices keys (en/ja/zh)
  * ports: add Connected devices column, devRender/devWalk via walkL2 (8c3df64)
  * config: add syslog port, extended stp/igmp/bw patterns, conf_toggle handling
  * system: show /cmd output in console (console_out), 401 handling
  * l2: hide delete for CPU entry, delL2Button, sort/filter improvements
  * navigation: add STP entry
- SPA: integrate new STP page (stp.html/js) as page-stp section with sectionInits.stp, bridge/prio handling, per-port config/status tables
- Remove per-page HTML/JS files (now bundled) and standalone stp.html/js

Verified: tools build, html_min generation (main.js 93k->81k->19k gzip, 8 files), html_data.h gzip sizes, main.js syntax check.
2026-09-06 04:45:46 +09:00
eraiza0816 37eefb4b35 Merge upstream/main into feat/webui-compression
Resolve the conflicts against the 41 upstream commits since 52692d0:

- Makefile: keep the upstream parallel-build fixes (grouped html_data
  target with order-only deps, create_build_dir, crtbank, deterministic
  BUILD_DATE, atomic version.h write) and keep the minify + gzip
  pipeline (-z, html_min, .css/.ico in the html file list) on top.
- html: l2.js stays removed; port the upstream L2 fixes into the
  single-page main.js (walkL2() with empty-reply and 4096-entry cap
  handling, 3 retries on error).  The L2 poll now re-schedules itself
  after each walk and stops when the section is hidden.
- httpd/httpd.c merged cleanly (gzip Content-Encoding alongside the
  upstream GET walk bound and counters.json port check).
- doc/webui-compression.md: describe the upstream version.h write.

Verified: KP_9000_6XHML_X2 and PCB_K0402WS_V3 build sequentially and
with make -j8; all 8 embedded files decompress byte-identically;
httpd_sim serves main.js/index.html and the L2 JSON endpoint.
2026-08-17 22:53:06 +09:00
eraiza0816 3f5093f180 Delete: go-lang 2026-08-17 22:18:08 +09:00
eraiza0816 4c4232cc07 fix(makefile): undo 2026-08-12 14:15:44 +09:00
eraiza0816 c4928e5546 docs(webui): describe the single-page layout and updated numbers
The Web UI is now 8 files at 25,302 bytes gzip (23.0 % of the raw
109,814); section switching measured on a real device.
2026-08-11 06:38:55 +09:00
eraiza0816 67ae4598d9 feat(webui): turn the Web UI into a single-page application
index.html now contains all pages as sections (overview, ports, stat,
vlan, l2, mirror, lag, eee, bandwidth, system, update); the sidebar
switches between them via the URL hash (#/ports, #/vlan, ...).
login.html stays separate as the authentication gate.

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 checkbox builders (lag/vlan/mirror) and the L2
scanner were made idempotent so re-initialising cannot duplicate rows.

tools/minify.py: recognise regex literals - an escaped slash before the
closing delimiter was previously stripped as a // comment.

Measured (make MACHINE=KP_9000_6XHML_X2): 8 embedded files, 109,814 ->
97,730 -> 25,302 bytes gzip (23.0 %); the data block ends at 0x462d6
(~86 KB freed).  On a real PCB-K0402WS-V3: cold load ~450 ms, section
switches ~60-160 ms with no asset re-downloads, all sections render
without JavaScript errors.
2026-08-11 06:37:20 +09:00
eraiza0816 b651890a3a docs(webui): update the compression numbers for the merged bundle
The bundle merge brings the Web UI to 28,200 bytes gzip (25.3 % of the
raw 111,280); the table of embedded files now has 18 entries.
2026-08-11 05:40:58 +09:00
eraiza0816 caa7874bb3 feat(webui): consolidate the page scripts into a single main.js bundle
All 17 page scripts (i18n, shared helpers, navigation and the per-page
scripts) are merged into one html/main.js that every page loads as its
only script, so the browser fetches it once and gzip can compress
across all of it.

The page-specific initialisation is guarded by the presence of the
matching page elements (it used to be selected implicitly by which
script a page loaded); the sidebar fill and the stat popup wiring moved
into DOMContentLoaded handlers because the bundle loads in <head>,
before the body exists.

tools/httpd_sim.h: BUFFER_SIZE 24000 -> 131072 so the simulator can
serve the larger bundle (the firmware is unaffected, it serves the
gzip'd bundle of ~17 KB).

Measured (make MACHINE=KP_9000_6XHML_X2): the JS drops from 23,302 to
16,948 bytes gzip; the whole Web UI from 34,784 to 28,200 bytes (25.3 %
of the raw 111,280); the data block ends at 0x46e28.  Verified with a
headless browser against httpd_sim and against a real PCB-K0402WS-V3:
all pages render, the sidebar and the port strip are drawn, no
JavaScript errors.
2026-08-11 05:40:01 +09:00
eraiza0816 53f33bd606 docs(webui): describe the minify + gzip asset compression
Pipeline, measured per-stage sizes (113,015 -> 101,498 -> 34,784 bytes)
and how to verify a build against the embedded image.
2026-08-11 03:06:45 +09:00
eraiza0816 57e3fce95e feat(webui): minify and gzip-compress the embedded web assets
Two build-time steps shrink the embedded Web UI (html/) from 113,015 to
34,784 bytes (30.8 %):

- tools/minify.py (new): string-literal-safe minifier that strips
  comments and indentation; comments are pure overhead on the wire, so
  the transfer size drops already before gzip (113,015 -> 101,498).
  Binary files (.ico) are copied through unchanged.  The output goes to
  output/html_min/, the new fileadder input.
- fileadder -z: gzip-compresses every added file (zlib gzip format,
  Z_BEST_COMPRESSION); the generated f_data entries carry a gzip flag.
  tools/Makefile links the tool against -lz.
- httpd: adds "Content-Encoding: gzip" for flagged entries; the 8051
  only copies compressed bytes, decompression happens in the browser.
  The JSON API stays uncompressed.

Fixes required to make the sizes deterministic:
- fileadder terminated files at data_read + 1, so the strlen()-based
  size computation depended on uninitialised buffer content; the NUL
  now lands directly after the content.
- version.h was .PHONY, so every build rewrote it while parallel builds
  could read a half-written copy.
- the file list was unscoped and missed .css/.ico; it is now scoped to
  html/ and covers all embedded types.

Measured with make MACHINE=KP_9000_6XHML_X2: data block end moves from
0x5b977 to 0x487e0 (~78 KB freed on a 512 KB image).  Verified with
MACHINE=PCB_K0402WS_V3 and on real hardware (PCB-K0402WS-V3).
2026-08-11 03:04:19 +09:00
153 changed files with 10376 additions and 5149 deletions

No files matched your search

+10 -3
View File
@@ -3,6 +3,7 @@ name: Build firmware
on:
push:
branches: ['**']
pull_request:
jobs:
build:
@@ -16,8 +17,14 @@ jobs:
- name: Install dependencies
run: |
apt update
apt install make gcc sdcc xxd python-is-python3 libjson-c-dev -y
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
+12
View File
@@ -4,10 +4,22 @@
## Added
- 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 -1
View File
@@ -7,8 +7,8 @@ RUN apt-get update && apt-get install -y \
xxd \
python3 \
libjson-c-dev \
golang-go \
git \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
# git safe.directory for mounted repos (Makefile uses git describe)
+47 -11
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
@@ -59,6 +67,8 @@ SRCS = \
dhcp.c \
html_data.c \
rtlplayground.c \
boot.c \
sfp.c \
syslog.c \
udp_apps.c
@@ -69,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
@@ -87,16 +100,29 @@ SRCS += \
OBJS = ${SRCS:%.c=$(BUILDDIR)/%.rel}
DEPS := ${SRCS:%.c=$(BUILDDIR)/%.d}
HTML := $(shell find html -name '*.js' -or -name '*.html' -or -name '*.svg')
HTML := $(shell find html -name '*.js' -or -name '*.html' -or -name '*.svg' -or -name '*.css' -or -name '*.ico')
html_data.c html_data.h &: $(HTML) | tools
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -d html -p html_data
# Minified copy of the web UI sources, used as the fileadder input.
# The raw html/ sources stay untouched for development; the minified
# copy is a build artifact under output/.
HTML_MIN := output/html_min
.PHONY: html_min
html_min: $(HTML)
rm -rf $(HTML_MIN)
mkdir -p $(HTML_MIN)
@for f in $(HTML); do python3 tools/minify.py $$f $(HTML_MIN)/$$(basename $$f) || exit 1; done
$(VERSION_HEADER):
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): 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
@@ -114,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-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 $< $@
@@ -132,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) -d html -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:
+20 -18
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
@@ -59,7 +59,7 @@ device simulator is provided, which runs entirely under Linux as a local webserv
Install the following particular build requisites (Debian 12/13), note that Ubuntu 24.04
still has an older version of sdcc, but you will need sdcc version 4.5 for the code to compile:
```
sudo apt install make gcc sdcc xxd python-is-python3 libjson-c-dev
sudo apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev
```
<details>
@@ -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)
@@ -175,11 +175,11 @@ Header checksum is: 0x5a1
## (3) Sandbox Usage with Ghidra (optional)
You can play with the image using ghidra or flash real Switch Hardware. For
ghidra see this information about [Ghidra images](ghidra.md).
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)
+479
View File
@@ -0,0 +1,479 @@
#pragma codeseg BANK3
#pragma constseg BANK3
#include <8051.h>
#include <stdint.h>
#include <stdbool.h>
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_common.h"
#include "rtl837x_flash.h"
#include "rtl837x_pins.h"
#include "rtl837x_phy.h"
#include "rtl837x_port.h"
#include "rtl837x_init.h"
#include "machine.h"
#include "phy.h"
#include "boot.h"
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 const uint8_t * __code const hex;
void sds_config_mac(uint8_t sds, uint8_t mode) __banked
{
reg_read_m(RTL837X_REG_SDS_MODES);
sfr_data[0] = 0;
sfr_data[1] = 0;
switch (sds) {
case 0:
sfr_mask_data(0, 0x1f, mode);
break;
case 1:
sfr_mask_data(0, 0xe0, mode << 5);
sfr_mask_data(1, 0x03, mode >> 3);
break;
case 2:
sfr_mask_data(1, 0xfc, 0x02 << 2);
}
if (machine_detected.isRTL8373) // Set 3rd SERDES Mode to 0x2 for RTL8224
sfr_mask_data(1, 0xfc, 0x02 << 2);
else
sfr_data[2] &= 0x03;
reg_write_m(RTL837X_REG_SDS_MODES);
print_string("\nRTL837X_REG_SDS_MODES: ");
print_reg(RTL837X_REG_SDS_MODES);
print_string("\n");
}
void early_boot_handle_button(void) __banked
{
if (machine.reset_pin == GPIO_NA)
return;
gpio_input_setup(machine.reset_pin);
// Debounce after init
delay(100);
// If the button is not already held at boot, continue normally.
if (gpio_pin_test(machine.reset_pin))
return;
set_sys_led_state(SYS_LED_FAST);
print_string("\n[Reset button held at boot]\n");
if (gpio_pin_test(machine.reset_pin))
return;
const __xdata uint32_t min_hold_ticks = 10UL * SYS_TICK_HZ;
const __xdata uint32_t max_hold_ticks = 30UL * SYS_TICK_HZ;
const __xdata uint32_t blink_ticks = SYS_TICK_HZ / 10; // 100 ms
const __xdata uint32_t pause_ticks = SYS_TICK_HZ / 2; // 500 ms
__xdata uint32_t start_ticks = ticks;
__xdata uint32_t last_blink_step = start_ticks;
__xdata uint8_t blink_step = 0;
set_sys_led_state(SYS_LED_ON);
while (!gpio_pin_test(machine.reset_pin)) {
__xdata uint32_t held_ticks = ticks - start_ticks;
if (held_ticks > max_hold_ticks) {
print_string("[Button held >30s at boot; continuing normal boot]\n");
return;
}
// Double blink pattern while button is held:
// ON (100ms), OFF (100ms), ON (100ms), OFF (500ms)
__xdata uint32_t step_ticks = (blink_step == 3) ? pause_ticks : blink_ticks;
if ((ticks - last_blink_step) >= step_ticks) {
blink_step = (blink_step + 1) & 0x3;
set_sys_led_state((blink_step == 0 || blink_step == 2) ? SYS_LED_ON : SYS_LED_OFF);
last_blink_step = ticks;
}
PCON |= 1;
}
set_sys_led_state(SYS_LED_ON);
if ((ticks - start_ticks) >= min_hold_ticks) {
print_string("[Button held 10s-30s at boot; restoring default config]\n");
set_sys_led_state(SYS_LED_FAST);
flash_default_config();
delay(3UL * SYS_TICK_HZ);
}
}
/*
* Configure the SerDes of the SoC for a particular mode
* to connect to an SFP module or a PHY
* Valid modes are SDS_10GR, SDS_QXGMII, SDS_HISGMII, SDS_HSG, SDS_SGMII and SDS_1000BX_FIBER
* The SerDes ID may be 0 or 1 for RTL8272 and 0-2 for RTL8373
* SDS_QXGMII is used for 10G Fiber, RTL8224 and RTL8261BE
*/
void sds_config(uint8_t sds, uint8_t mode) __banked
{
print_string("sds_config sds: "); print_byte(sds); print_string(", mode: "); print_byte(mode); write_char('\n');
sds_config_mac(sds, mode);
if (mode == SDS_10GR || mode == SDS_QXGMII)
sds_write_v(sds, 0x21, 0x10, 0x4480); // Q002110:6480
else
sds_write_v(sds, 0x21, 0x10, 0x6480); // Q002110:6480
sds_write_v(sds, 0x21, 0x13, 0x0400); // Q002113:0400
sds_write_v(sds, 0x21, 0x18, 0x6d02); // Q002118:6d02
sds_write_v(sds, 0x21, 0x1b, 0x424e); // Q00211b:424e
sds_write_v(sds, 0x21, 0x1d, 0x0002); // Q00211d:0002
sds_write_v(sds, 0x36, 0x1c, 0x1390); // Q00361c:1390
sds_write_v(sds, 0x36, 0x14, 0x003f); // Q003614:003f
uint8_t page = 0;
uint16_t v = 0;
switch (mode) {
case SDS_SGMII:
case SDS_1000BX_FIBER:
v = 0x0300;
page = 0x24;
break;
case SDS_HISGMII:
case SDS_HSG:
v = 0x0200;
page = 0x28;
break;
case SDS_10GR:
case SDS_QXGMII:
v = 0x0200;
page = 0x2e;
break;
case SDS_100FX:
v = 0x0200;
page = 0x26;
break;
default:
print_string("Error in SDS Mode\n");
return;
}
sds_write_v(sds, 0x36, 0x10, v); // Q003610:0200
if (page == 0x2e) { // 10G Fiber / SDS_QXGMII
sds_write_v(sds, page, 0x04, 0x0080); // Q012e04:0080
sds_write_v(sds, page, 0x06, 0x0408); // Q012e06:0408
sds_write_v(sds, page, 0x07, 0x020d); // Q012e07:020d
sds_write_v(sds, page, 0x09, 0x0601); // Q012e09:0601
sds_write_v(sds, page, 0x0b, 0x222c); // Q012e0b:222c
sds_write_v(sds, page, 0x0c, 0xa217); // Q012e0c:a217
sds_write_v(sds, page, 0x0d, 0xfe40); // Q012e0d:fe40
sds_write_v(sds, page, 0x15, 0xf5c1); // Q012e15:f5c1
} else {
sds_write_v(sds, page, 0x04, 0x0080); // Q002804:0080
sds_write_v(sds, page, 0x07, 0x1201); // Q002807:1201
sds_write_v(sds, page, 0x09, 0x0601); // Q002809:0601
sds_write_v(sds, page, 0x0b, 0x232c); // Q00280b:232c
sds_write_v(sds, page, 0x0c, 0x9217); // Q00280c:9217
sds_write_v(sds, page, 0x0f, 0x5b50); // Q00280f:5b50
sds_write_v(sds, page, 0x15, 0xe7c1); // Q002815:e7f1 BUG !
}
sds_write_v(sds, page, 0x16, 0x0443); // Q002816:0443 / Q012e16:0443
sds_write_v(sds, page, 0x1d, 0xabb0); // Q00281d:abb0 / Q012e1d:abb0
sds_write_v(sds, 0x06, 0x12, 0x5078); // Q000612:5078
sds_write_v(sds, 0x07, 0x06, 0x9401); // Q000706:9401
sds_write_v(sds, 0x07, 0x08, 0x9401); // Q000708:9401
sds_write_v(sds, 0x07, 0x0a, 0x9401); // Q00070a:9401
sds_write_v(sds, 0x07, 0x0c, 0x9401); // Q00070c:9401
sds_write_v(sds, 0x1f, 0x0b, 0x0003); // Q001f0b:0003
sds_write_v(sds, 0x06, 0x03, 0xc45c); // Q000603:c45c
// RTL8261BE
if (machine.n_10g && mode == SDS_QXGMII) {
sds_write_v(sds, 0x06, 0x1f, 0x2100); // Q00061f:2100
sds_write_v(sds, 0x07, 0x11, 0x054f); // Q000711:054f
sds_write_v(sds, 0x20, 0x00, 0x0030); // Q002000:0030
sds_write_v(sds, 0x20, 0x00, 0x0010); // Q002000:0010
sds_write_v(sds, 0x20, 0x00, 0x0050); // Q002000:0050
sds_write_v(sds, 0x20, 0x00, 0x00d0); // Q002000:00d0
sds_write_v(sds, 0x20, 0x00, 0x0cd0); // Q002000:0cd0
sds_write_v(sds, 0x20, 0x00, 0x04d0); // Q002000:04d0
sds_write_v(sds, 0x20, 0x00, 0x04d0); // Q002000:04d0
sds_write_v(sds, 0x20, 0x00, 0x0cd0); // Q002000:0cd0
sds_write_v(sds, 0x20, 0x00, 0x00d0); // Q002000:00d0
sds_write_v(sds, 0x20, 0x00, 0x00d0); // Q002000:00d0
sds_write_v(sds, 0x20, 0x00, 0x0050); // Q002000:0050
sds_write_v(sds, 0x20, 0x00, 0x0010); // Q002000:0010
sds_write_v(sds, 0x20, 0x00, 0x0010); // Q002000:0010
sds_write_v(sds, 0x20, 0x00, 0x0030); // Q002000:0030
sds_write_v(sds, 0x20, 0x00, 0x0000); // Q002000:0000
sds_write_v(sds, 0x1f, 0x00, 0x000b); // Q001f00:000b
sds_write_v(sds, 0x1f, 0x00, 0x0000); // Q001f00:0000
return;
}
if (mode != SDS_QXGMII)
sds_write_v(sds, 0x06, 0x1f, 0x2100); // Q00061f:2100
if (mode == SDS_1000BX_FIBER) {
sds_write_v(sds, 0x02, 0x04, 0x0020); // Q000204:0020
sds_write_v(sds, 0x00, 0x02, 0x73d0); // Q000002:73d0
sds_write_v(sds, 0x00, 0x04, 0x074d); // Q000004:074d
sds_write_v(sds, 0x20, 0x04, 0x0000); // Q002000:0000
sds_write_v(sds, 0x1f, 0x00, 0x0000); // Q001f00:0000
}
}
void rtl8224_enable(void) __banked
{
// Set Pin 4 low
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(4);
// Set pin 4 high
reg_bit_set(RTL837X_REG_GPIO_32_63_OUTPUT, 4);
rtl8224_release_tick = ticks;
}
void nic_setup(void) __banked
{
// Enable NIC
// r6040:00000100 R6040-00001100
reg_bit_set(RTL837X_REG_HW_CONF, 0xc);
// This sets the size of the RX buffer, the filling level is in 0x7874
// R7848-000004ff
REG_SET(RTL837X_REG_NIC_RXBUFF_RX, 0x4ff);
// R7844-000007fe
REG_SET(RTL837X_REG_NIC_BUFFSIZE_TX, 0x7fe);
// Configure NIC RX to receive various types of packets
// RTL837X_REG_RX_CTRL: Set bits 24-31 to 0x4, clear bits 16/17
reg_read_m(RTL837X_REG_RX_CTRL);
sfr_mask_data(3, 0xff, 0x04);
sfr_mask_data(2, 0x03, 0);
reg_write_m(RTL837X_REG_RX_CTRL);
// Enable NIC TX (set bit 0)
reg_bit_set(RTL837X_REG_TX_CTRL, 0);
// Enable NIC RX (set bit 0)
reg_bit_set(RTL837X_REG_RX_CTRL, 0);
// Drop packets with invalid CRC
reg_bit_clear(RTL837X_REG_RX_CTRL, 2);
// R603c-00000200
// CPU-port is CPU-Tag aware (bit 9)
REG_SET(RTL837X_REG_CPU_TAG_AWARE_PMASK, 0x200);
// Insert CPU-tag for internally received packets (bit 0), MODE is 0, i.e. ALL packets (bits 8-9)
reg_read_m(RTL837X_REG_CPU_TAG);
sfr_mask_data(0, 1, 1);
sfr_mask_data(1, 3, 0);
reg_write_m(RTL837X_REG_CPU_TAG);
// Force MAC mode of the CPU port (port 9)
// r6368:00000194 R6368-00000197
reg_read_m(RTL837X_REG_MAC_FORCE_MODE + 9 * 4);
sfr_mask_data(0, 0, 3); // Set bits 0, 1: Force link
reg_write_m(RTL837X_REG_MAC_FORCE_MODE+ 9 * 4);
// Sequence number of TX packets
tx_seq = 0;
}
void rtl8373_revision(void) __banked
{
reg_read_m(RTL837X_REG_CHIP_INFO);
sfr_mask_data(2, 0x0a, 0x0a); // Enable reading version
reg_write_m(RTL837X_REG_CHIP_INFO);
delay(50);
reg_read_m(RTL837X_REG_CHIP_INFO);
print_string("CPU revision: "); print_byte(sfr_data[2]); print_byte(sfr_data[2]); write_char('\n');
sfr_mask_data(2, 0x0a, 0x00); // Enable reading version
reg_write_m(RTL837X_REG_CHIP_INFO);
}
/*
* The SoC manages Link-State for steering the LEDs and can set PHY-settings
* automatically through Realtek's SMI (Simple Managagement) Interface, a
* proprietary version of MDIO which for example allows for more PHYs on the same
* bus.
* Configure polling via SMI and the interface setup during boot.
*/
void init_smi(void) __banked
{
print_string("\ninit_switch called\n");
/* Set the SMI(i.e.I2C) type for PHY polling, 0b01 is 2.5/10G PHY. Disable (0b00) for the SFP-ports
* which are at port 8 and additionally at port 3 for a dual SFP device
*/
// Default: 0x00005555
// Workaround for SDCC BUG 4070: SFR_DATA_U32 = 0x00005555;
SFR_DATA_U16_UPPER = 0x0000;
SFR_DATA_U16 = 0x5555;
if (machine.n_10g == 2) {
// 0x00015555, only change the bytes that differs from the default.
SFR_DATA_16 = 0x01;
} else if (machine.n_sfp == 2)
// 0x00005515
SFR_DATA_0 = 0x15;
reg_write(RTL837X_REG_SMI_MAC_TYPE);
// Configure polling of all PHYs by the MAC to detect link-state changes
// Default: 0x000000ff
// Workaround for SDCC BUG 4070: SFR_DATA_U32 = 0x000000ff;
SFR_DATA_U16_UPPER = 0x0000;
SFR_DATA_U16 = 0x00ff;
if (!machine_detected.isRTL8373) {
if (machine.n_sfp == 2) {
// 0x000000f0, only change the bytes that differs from the default.
SFR_DATA_0 = 0xf0;
} else {
// 0x000001f8, only change the bytes that differs from the default.
SFR_DATA_8 = 0x01;
SFR_DATA_0 = 0xf8;
}
}
reg_write(RTL837X_REG_SMI_PORT_POLLING);
// Enable MDC
reg_read_m(RTL837X_REG_SMI_CTRL);
sfr_mask_data(1, 0, 0x70); // Set bits 12-14 to enable MDC for SMI0-SMI2
reg_write_m(RTL837X_REG_SMI_CTRL);
delay(50);
if (!machine_detected.isRTL8373) {
// Change I2C addresses for SMI of the non-existent PHYs
// r6450:000020e6 R6450-000000e6
reg_read_m(RTL837X_REG_SMI_PORT6_9_ADDR);
sfr_mask_data(1, 0x7c, 0);
reg_write_m(RTL837X_REG_SMI_PORT6_9_ADDR);
// r644c:0a418820 R644c-0a400820
reg_read_m(RTL837X_REG_SMI_PORT0_5_ADDR);
sfr_mask_data(2, 0x0f, 0);
sfr_mask_data(1, 0x80, 0);
reg_write_m(RTL837X_REG_SMI_PORT0_5_ADDR);
}
if (machine.n_10g == 2) {
// Set address of second external PHY on port 8
REG_SET(RTL837X_REG_SMI_PORT6_9_ADDR, 0x000040e6);
}
}
void setup_i2c(void) __banked
{
REG_SET(RTL837X_REG_I2C_MST_IF_CTRL, 0);
// Configure SFP EEPROM address (0x50) as I2C device address
// Configure SFP readings address (0x51) as I2C device address
REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16), 0x50 >> 5, (0x50 << 3) & 0xff);
REG_SET(RTL837X_REG_I2C_CTRL2, 0);
// 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++) {
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');
// `& 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);
break;
case 2:
// Bit 15-16 0b01 -> SCL
sfr_mask_data(1, 0x80, 0x80);
sfr_mask_data(2, 0x01, 0x00);
break;
case 1:
// Bit 11-12 0b01 -> SCL
sfr_mask_data(1, 0x18, 0x08);
break;
case 0:
// Bit 7-8 0b01 -> SCL
sfr_mask_data(0, 0x80, 0x80);
sfr_mask_data(1, 0x01, 0x00);
break;
}
switch (sda_bus) {
case 4:
// Bit 29 0b0 -> SDA
sfr_mask_data(3, 0x20, 0x00);
break;
case 3:
// Bit 5-6 0b10 -> SDA (implies enabled SCL on bus 3)
sfr_mask_data(0, 0x60, 0x40);
break;
case 2:
// Bit 17-18 0b01 -> SDA
sfr_mask_data(2, 0x06, 0x02);
break;
case 1:
// Bit 13-14 0b01 -> SDA
sfr_mask_data(1, 0x60, 0x20);
break;
case 0:
// Bit 9-10 0b01 -> SDA
sfr_mask_data(1, 0x06, 0x02);
break;
}
}
reg_write_m(RTL837X_PIN_MUX_1);
}
/* Give the switch a name carrying the tail of its MAC, so several of them on
* one network are distinguishable out of the box. Called after the startup
* config has been replayed and returns at once if that config already set a
* name, so a configured switch does no work for it (suggested in review).
*
* Written without a loop on purpose. Locals - counters and pointers alike -
* land in the 8051's internal-RAM overlay, and on an image with LACP and STP
* both enabled that overlay is exhausted: a loop here makes the linker fail
* with "Could not get 8 consecutive bytes in internal RAM for area OSEG".
* Moving the code into its own function does not help; the overlay is shared
* across the whole image. Hoisting the locals to xdata does not help either,
* because itohex() is inline and brings its own frame. */
void set_hostname_default(void) __banked
{
if (hostname[0] != NUL)
return;
strcpy((__xdata uint8_t *)hostname, "RTLPlayground-");
hostname[14] = hex[uip_ethaddr.addr[3] >> 4];
hostname[15] = hex[uip_ethaddr.addr[3] & 0xf];
hostname[16] = hex[uip_ethaddr.addr[4] >> 4];
hostname[17] = hex[uip_ethaddr.addr[4] & 0xf];
hostname[18] = hex[uip_ethaddr.addr[5] >> 4];
hostname[19] = hex[uip_ethaddr.addr[5] & 0xf];
hostname[20] = NUL;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef BOOT_H
#define BOOT_H
void sds_config_mac(uint8_t sds, uint8_t mode) __banked;
void sds_config(uint8_t sds, uint8_t mode) __banked;
void early_boot_handle_button(void) __banked;
void rtl8224_enable(void) __banked;
void nic_setup(void) __banked;
void rtl8373_revision(void) __banked;
void init_smi(void) __banked;
void setup_i2c(void) __banked;
void set_hostname_default(void) __banked;
#endif
+516 -83
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,32 +16,39 @@
#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"
#include "uip/uip.h"
#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];
@@ -49,9 +58,6 @@ __xdata char port_names[9][PORT_NAME_SIZE];
extern __xdata uint16_t management_vlan;
extern __xdata struct uip_eth_addr uip_ethaddr;
extern __xdata uint8_t sfp_speed[2];
extern __xdata uint8_t sfp_pins_last;
extern __xdata uint8_t sfp_options[2];
__xdata uint8_t gpio_last_value[8] = { 0 };
// Temporatly for str to hex convertion value.
@@ -61,11 +67,11 @@ __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;
__xdata uint8_t ip[4];
__xdata uint8_t mac_parse_result[6];
/* Scratch for parse_syslog(), in xdata: a local here would take internal RAM
* the linker has none of. */
@@ -106,8 +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;
@@ -349,7 +354,7 @@ err:
}
// Prints an IPv4 address.
void print_ip(__xdata uint8_t * ptr)
void print_ip(__xdata uint8_t * ptr) __banked
{
uint8_t idx = 0;
uint8_t num;
@@ -364,6 +369,125 @@ void print_ip(__xdata uint8_t * ptr)
}
}
// Prints a MAC address.
void print_mac(__xdata uint8_t *ptr) __banked
{
uint8_t idx = 0;
uint8_t num;
while (1) {
num = *ptr++;
print_byte(num);
if (++idx == 6)
break;
write_char(':');
}
}
// Parses a MAC address "aa:bb:cc:dd:ee:ff" (":" or "-" separators) from
// cmd_buffer starting at idx into the global mac_parse_result[6]. Each octet
// must be exactly two hex digits with a separator between octets and nothing
// trailing, so repeated separators, missing digits or a trailing hex digit are
// rejected. Returns the number of bytes consumed, or 0 on error.
uint8_t parse_mac(uint8_t idx)
{
uint8_t b = 0;
uint8_t c = ':';
do {
if (c != ':' && c != '-')
goto err;
__bit again = true;
uint8_t v = 0;
while(1) {
c = cmd_buffer[idx++];
c -= '0';
if (c > 9) {
c |= 0x20;
c += '0' - 'a';
if (c > 5)
goto err;
c += 10;
}
// force swap-instr.
v = (v << 4) | (v >> 4);
v |= c;
if (again)
again = false;
else
break;
}
mac_parse_result[b++] = v;
c = cmd_buffer[idx++];
} while(b < 6);
if (c == ' ' || c == NUL)
return idx;
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
// re-registers the static L2 management entry so the change applies without a
// reboot. As a regular command a "mac ..." line in the startup config applies
// on every boot (execute_config runs before the final static L2 entry).
void parse_mac_cmd(void)
{
if (cmd_words_len == 1) {
print_string("Current MAC: ");
print_mac(uip_ethaddr.addr);
write_char('\n');
return;
}
if (cmd_words_len != 2 || !parse_mac(cmd_words_b[1])) {
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)) {
cmd_error("the MAC must be unicast and globally administered\n");
return;
}
if (memcmp(mac_parse_result, uip_ethaddr.addr, 6) == 0) {
print_string("MAC unchanged\n");
return;
}
// Swap the static management entry over to the new address: remove the
// old one (still in uip_ethaddr) before overwriting it.
port_l2_static_mgmt(uip_ethaddr.addr, management_vlan, true);
memcpy(uip_ethaddr.addr, mac_parse_result, 6);
port_l2_static_mgmt(uip_ethaddr.addr, management_vlan, false);
print_string("MAC set to: ");
print_mac(uip_ethaddr.addr);
write_char('\n');
print_string("Save the startup config to keep it across reboots\n");
}
void parse_lag(void)
{
@@ -422,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");
}
@@ -464,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");
}
@@ -504,19 +628,29 @@ void parse_vlan(void)
}
uint8_t w = 2;
if (cmd_words_len > w && isletter(cmd_buffer[cmd_words_b[w]])) {
uint8_t pos = cmd_words_b[w];
if (cmd_words_len > w && isletter(cmd_buffer[pos])) {
uint8_t i = 0;
vlan_name_remove(vlan_settings.vlan);
vlan_names[vlan_ptr++] = hex[(vlan_settings.vlan >> 8) & 0xf];
vlan_names[vlan_ptr++] = hex[(vlan_settings.vlan >> 4) & 0xf] ;
vlan_names[vlan_ptr++] = hex[vlan_settings.vlan & 0xf];
while(!cmd_is_space_or_nul(cmd_words_b[w] + i)) {
write_char(cmd_buffer[cmd_words_b[w] + i]);
vlan_names[vlan_ptr++] = cmd_buffer[cmd_words_b[w] + i++];
while (!cmd_is_space_or_nul(pos + i))
i++;
if (vlan_ptr + i > (VLAN_NAMES_SIZE - 5)) {
print_string("VLAN name table full, name ignored\n");
} else {
vlan_name_remove(vlan_settings.vlan);
__xdata uint8_t *vlan_str = &vlan_names[vlan_ptr];
*vlan_str++ = hex[(vlan_settings.vlan >> 8) & 0xf];
*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 = name_char(cmd_buffer[pos++]);
write_char(c);
*vlan_str++ = c;
}
*vlan_str++ = ' '; *vlan_str = NUL;
vlan_ptr = vlan_str - vlan_names;
print_string("<\n");
}
vlan_names[vlan_ptr++] = ' '; vlan_names[vlan_ptr] = NUL;
w++;
print_string("<\n");
}
uint8_t ret;
@@ -552,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");
}
@@ -603,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");
}
@@ -640,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: ");
@@ -651,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);
@@ -672,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)
@@ -737,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;
}
@@ -745,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;
@@ -766,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")) {
@@ -831,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");
}
}
@@ -859,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,
@@ -868,7 +1014,7 @@ void parse_mtu(void)
return;
err:
print_string("mtu [port] [size]\n");
cmd_error("mtu [port] [size]\n");
return;
}
@@ -925,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;
}
@@ -955,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");
}
@@ -987,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;
}
@@ -1034,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);
}
@@ -1077,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;
}
@@ -1133,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;
}
@@ -1182,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;
}
@@ -1242,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;
}
@@ -1277,7 +1449,7 @@ void parse_passwd(void)
passwd[j] = NUL;
return;
}
print_string("Missing password\n");
cmd_error("Missing password\n");
}
@@ -1300,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;
@@ -1320,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;
}
}
@@ -1340,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");
}
}
@@ -1411,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)
@@ -1449,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
@@ -1460,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
@@ -1476,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.
@@ -1646,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");
@@ -1662,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");
}
}
@@ -1676,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');
@@ -1692,8 +1934,11 @@ 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")) {
/* "hostname" alone reports the current name; "hostname <text>"
* sets it, sanitized to JSON-safe printable ASCII. A name with
@@ -1709,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")) {
@@ -1740,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")) {
@@ -1760,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")) {
@@ -1782,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 {
@@ -1833,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;
@@ -1843,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;
@@ -1864,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--;
@@ -1882,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) {
@@ -1892,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)) {
@@ -1911,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
+30 -2
View File
@@ -6,14 +6,42 @@
#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;
void execute_commands(__xdata uint8_t *p) __banked;
void print_ip(__xdata uint8_t *ptr);
void print_ip(__xdata uint8_t *ptr) __banked;
void print_mac(__xdata uint8_t *ptr) __banked;
void print_sw_version(void) __banked;
void clear_command_history(void) __banked;
+7 -1
View File
@@ -3,6 +3,8 @@
;
.globl _crc_value
.globl _crc16
.globl _crc16_bank1
.globl __sdcc_banked_ret
.equ BANK, 0x96
; .equ DPS, 0x86
; Variable in XMEM holding current CRC16 value, being updated
@@ -18,7 +20,7 @@ _crc_value::
.area HOME (CODE)
.area CSEG (CODE)
; .area BANK1 (CODE)
_crc16:
_crc16_bank1:
mov BANK, #1
push dph
push dpl
@@ -52,6 +54,10 @@ _crc16:
.area BANK1 (CODE)
_crc16:
lcall _crc16_bank1
ljmp __sdcc_banked_ret
crc16_table_l:
.byte #0x00, #0xc1, #0x81, #0x40, #0x01, #0xc0, #0x80, #0x41
.byte #0x01, #0xc0, #0x80, #0x41, #0x00, #0xc1, #0x81, #0x40
+36 -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;
@@ -332,6 +332,18 @@ void parse_dhcp(void)
uip_ipaddr(&uip_hostaddr, dhcp_state.current_ip[0], dhcp_state.current_ip[1], dhcp_state.current_ip[2], dhcp_state.current_ip[3]);
uip_ipaddr(&uip_draddr, dhcp_state.router[0], dhcp_state.router[1], dhcp_state.router[2], dhcp_state.router[3]);
uip_ipaddr(&uip_netmask, dhcp_state.subnet[0], dhcp_state.subnet[1], dhcp_state.subnet[2], dhcp_state.subnet[3]);
/* T1 is optional. Without it the renewal time stays zero, which
* makes dhcp_timer zero, and the callback then sends a REQUEST on
* every pass through LEASING: client and server trade REQUEST and
* ACK as fast as the round trip allows. RFC 2131 has the client
* renew at half the lease when the server does not say, and if
* there is no lease either, pick something rather than zero. Only
* filled in when absent, so a server that asks for a short renewal
* still gets it. */
if (!dhcp_state.renewal)
dhcp_state.renewal = dhcp_state.lease / 2;
if (!dhcp_state.renewal)
dhcp_state.renewal = 60;
dhcp_state.state = DHCP_LEASING;
dhcp_state.ticks = SYS_TICK_HZ;
dhcp_state.dhcp_timer = dhcp_state.renewal > 0xffff ? 0xffff : dhcp_state.renewal;
@@ -341,6 +353,12 @@ void parse_dhcp(void)
void dhcp_start(void) __banked
{
/* Starting twice would leave the first socket bound: uip_udp_new() takes
* the next free slot while the old one keeps port 68, and dhcp_callback()
* matches on that port rather than on the connection, so every leftover
* runs the state machine too and the timers count down once per socket. */
if (dhcp_state.state != DHCP_OFF)
dhcp_stop();
uip_ipaddr(server, 255,255,255,255);
dhcp_state.conn = uip_udp_new(&server, HTONS(DHCPC_SERVER_PORT));
dhcp_state.current_ip[0] = dhcp_state.current_ip[1] = dhcp_state.current_ip[2] = dhcp_state.current_ip[3] = 0;
@@ -358,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.
+32
View File
@@ -0,0 +1,32 @@
# MokerLink POE-2G080110GS
The `POE-2G080110GS` is an unmanaged 8x2.5G RJ45 and 1xSFP+ PoE switch sold by Mokerlink. There is also a `POE-2G080110GSM` managed version of the switch which may be similar but this has not been verified on actual hardware.
## Brands
| Brand | Type |Managed| PCB | Flash | Chip RTL |
|--------|------------------|-------|------------------|-----------------|---------------|
| Mokerlink | POE-2G080110GS | No | 2M-PCB51-ML-V1.1 | 4MB (W25Q32JV) | 8373N + 8224N |
## Hardware overview
Front
<img src="photos/POE-2G080110GS/POE-2G080110GS-front.jpg" width="300" />
Label
<img src="photos/POE-2G080110GS/POE-2G080110GS-label.jpg" width="300" />
The stock firmware from a POE-2G080110GS device had a sha256sum of `4c280853465eaad80e1772075c0f2d29c11fb8c561547f06353d3a5443c388e9`.
### PCB
Top silkscreen is marked `2M-PCB51-ML-V1.1`. PoE is provided by RTL8238C. Flash chip is 4 MB Winbond 25Q32JV (U8).
<img src="photos/POE-2G080110GS/POE-2G080110GS-pcb.jpg" width="300" />
> [!CAUTION]
> This device operates on mains voltage, which can be a dangerous or potentially lethal hazard. Even after the device is unplugged, high-voltage capacitors can remain charged afterward and may still shock you. Do not attempt to open/disassemble a mains powered device unless you understand how to do so safely. Also, opening the device may void your warranty.
## Notes
The stock firmware blinks both the 2.5G and 1G LEDs on activity, despite the front of the device indicating that each LED should be independent. The `POE_2G080110GS` machine config behaves as the front of the device indicates (and what seems more logical) -- each port LED turns on and blinks independently.
+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

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 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.
+26
View File
@@ -58,6 +58,32 @@ port set to admit tagged frames only (`ingress <port>t`) never delivers one
to the CPU. `stp_setup()` prints a warning for every STP-enabled port in that
state.
## Link aggregation
A LAG (Link Aggregation Group) is handled like an additional port by the STP
protocol, with its own timers and states. The switch devices support up to 4
LAGs, which are shown alongside the physical ports in the STP status. A group
carries its own path cost, priority, edge and guard settings, and gets its own
row on the Spanning Tree page.
```
stp lag 1 cost 10000 # the group decides, not its members
stp lag 1 edge off
```
LAGs are configured via the `lag` command. Once a port is a member of a LAG it
can no longer be configured individually for STP, so `stp port <n>` on a member
tells you which group to configure instead. Membership is re-read from the
aggregation registers once a second, so a group changing under LACP is picked
up without any coordination between the two.
The switch hardware does not handle STP state for a LAG as a whole. State
changes for the members have to be done by the firmware, updating every member
port, which is possible using a single register write. BPDUs go out through the
lowest member and carry the group's own port id. Losing one member of a live
LAG is not a topology change; the logical port only goes down with its last
link.
Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port:
`00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in
blocking forwards nothing between ports, but it still sends what the CPU
+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`).
+10
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,10 +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 |
@@ -32,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
+108
View File
@@ -0,0 +1,108 @@
# Compressing the Web UI Assets
The Web UI (all files under `html/`) is embedded into the flash image and
served over HTTP. Two build-time steps keep it small: a safe minifier and
gzip compression of the embedded files.
## How it works
```
html/ --(minify.py)--> output/html_min/ --(fileadder -z)--> flash image
```
- `tools/minify.py` removes comments (`<!-- -->`, `//`, `/* */`) and
line-leading whitespace without touching string or regex literals, so
the output is functionally identical to the input. Binary files
(`.ico`) are copied through unchanged. The raw sources in `html/` stay
untouched for development; the minified copy in `output/` is a pure
build artifact. Comments and indentation are pure overhead for the
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
between via the URL hash. `login.html` stays separate because it is
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
final image. Both invocations run on `output/html_min/`, so the table
and the image always agree.
- The httpd serves each file with `Content-Encoding: gzip` according to
the `gzip` flag in its `f_data` entry. The 8051 only copies compressed
bytes from flash to the socket; decompression happens in the browser.
- Only the static files are compressed. The JSON API responses
(`/information.json`, `/vlanlist`, ...) are generated on the fly and
stay uncompressed.
## Measured effect
Build on any machine (the Web UI is machine-independent):
| | bytes |
|---|---:|
| raw sources | 109,814 |
| minified (comments and indentation removed) | 97,730 |
| gzip (flash + wire) | 25,302 (23.0 %) |
The minified assets are also what the browser receives when gzip support
is missing or disabled, so the transfer size drops in both stages:
109,814 → 97,730 bytes from minification, then → 25,302 bytes from gzip.
The embedded data block ends at `0x5b977` without compression and at
`0x462d6` with it, freeing ~86 KB of the 512 KB image.
The consolidation helps at every step. The 17 scripts gzip to 23,302
bytes as separate files but to 17,231 bytes as one `main.js` bundle,
because the gzip dictionary spans all of them. The single-page layout
then removes the per-page HTML and, more importantly, the repeated
downloads: with the multi-page UI the browser fetched `main.js` on every
navigation; the SPA fetches everything once and section switches are
pure in-page JavaScript (measured on a real device: cold load ~450 ms,
section switch ~60-160 ms, the only network traffic afterwards is the
JSON polling of the visible section).
## Notes
- The files stay well below the `uint16_t` size limit of the file table
(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.
- `version.h` is now written by a single atomic `printf` (change from
upstream) instead of five separate `echo`s, so a parallel build
(`make -j`) can no longer read a half-written copy. It stays in
`.PHONY` so it is regenerated on every build.
## Verification
Decompress what the firmware embeds and compare it with the minified
sources:
```python
import gzip, re
h = open('html_data.h').read(); c = open('html_data.c').read()
starts = dict((k, int(v, 16)) for k, v in
re.findall(r'#define FDATA_START_(\w+) 0x([0-9a-f]+)', h))
sizes = dict((k, int(v)) for k, v in
re.findall(r'#define FDATA_SIZE_(\w+) (\d+)', h))
entries = [(p, sizes[z], starts[s]) for p, s, z in
re.findall(r'\{"/([^"]+)", FDATA_START_(\w+), '
r'FDATA_SIZE_(\w+), \w+, 1\}', c)]
d = open('output/rtlplayground.bin', 'rb').read()
for name, size, start in entries:
assert gzip.decompress(d[start:start+size]) == \
open('output/html_min/' + name, 'rb').read()
```
`tools/httpd_sim` serves the raw files from `html/` and does not exercise
the gzip path; a browser session against a flashed switch (or `curl
--compressed`) is the final check.
+2393
View File
File diff suppressed because it is too large. Load diff
-21
View File
@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="bw_title">Ingress and Egress Bandwidth</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<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>
<script src="/bandwidth.js"></script>
</div>
<script src="/navigation.js"></script>
</body>
</html>
-138
View File
@@ -1,138 +0,0 @@
const iLayout = '" type="number" maxlength="10" size="10" onfocus="inputFocus(';
function createBW() {
var tbl = document.getElementById('bwtable');
const limit = '<input type="checkbox" id="limit_port" onchange="exec();">'
if (tbl.rows.length <= 2 && numPorts) {
console.log("CREATING TABLE ", tbl.rows.length);
for (let i = 2; i < 2 + numPorts; i++) {
const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i-1)));
td = tr.insertCell();
td.innerHTML = limit.replaceAll("limit_port", "ilimit_port_" + i).replace("exec()", "iClicked(" + i + ")");
td = tr.insertCell();
td.innerHTML = t('bw_unlimited');
td = tr.insertCell();
td.innerHTML = limit.replaceAll("limit_port", "fc_port_" + i).replace("exec()", "document.getElementById('bwapply_" + i + "').disabled=false;");
td = tr.insertCell();
td.innerHTML = limit.replaceAll("limit_port", "elimit_port_" + i).replace("exec()", "eClicked(" + i + ")");
td = tr.insertCell();
td.innerHTML = t('bw_unlimited');
var button = '<button type="button" id="bwapply_' + i + '" style="margin: 0 0 0 24px" onclick="applyBandwidth(' + i + ');">' + t('bw_col_apply') + '</button>';
td = tr.insertCell();
td.innerHTML = button;
document.getElementById("bwapply_" + i).disabled = true;
}
}
}
function iClicked(i)
{
document.getElementById("bwapply_" + i).disabled=false;
var tbl = document.getElementById('bwtable');
var tr = tbl.rows[i];
if (!document.getElementById("ilimit_port_" + i).checked) {
tr.cells[2].innerHTML = t('bw_unlimited');
document.getElementById("fc_port_" + i).disabled = true;
document.getElementById("fc_port_" + i).checked = true;
} else {
tr.cells[2].innerHTML = '<input id="ibw_' + i + iLayout + i + ')" value="0"/>';
document.getElementById("fc_port_" + i).disabled = false;
document.getElementById("fc_port_" + i).checked = true;
}
}
function eClicked(i)
{
document.getElementById("bwapply_" + i).disabled=false;
var tbl = document.getElementById('bwtable');
var tr = tbl.rows[i];
if (!document.getElementById("elimit_port_" + i).checked) {
tr.cells[5].innerHTML = t('bw_unlimited');
} else {
tr.cells[5].innerHTML = '<input id="ebw_' + i + iLayout + i + ')" value="0"/>';
}
}
function inputFocus(i)
{
document.getElementById("bwapply_" + i).disabled=false;
}
async function doCMD(cmd)
{
console.log("Sending >" + cmd + "<");
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
console.log('Completed!', response);
} catch(err) {
console.error(`Error: ${err}`);
}
}
async function applyBandwidth(i) {
var tbl = document.getElementById('bwtable');
var tr = tbl.rows[i];
var cmd = "bw in " + (i-1) + " off";
if (document.getElementById("ilimit_port_" + i).checked)
cmd = 'bw in ' + (i-1) + ' ' + parseInt(document.getElementById("ibw_" + i).value).toString(16).padStart(4, "0");;
doCMD(cmd);
if (document.getElementById("ilimit_port_" + i).checked) {
if (!document.getElementById("fc_port_" + i).checked)
cmd = "bw in " + (i-1) + " drop";
doCMD(cmd);
}
var cmd = "bw out " + (i-1) + " off";
if (document.getElementById("elimit_port_" + i).checked)
cmd = 'bw out ' + (i-1) + ' ' + parseInt(document.getElementById("ebw_" + i).value).toString(16).padStart(4, "0");;
doCMD(cmd);
}
function getBW() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
console.log("IN getBW ");
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
console.log("BW: ", JSON.stringify(s));
var tbl = document.getElementById('bwtable');
if (tbl.rows.length > 2 && numPorts) {
for (let i = 2; i < 2 + numPorts; i++) {
p = s[i-2];
let n = p.portNum;
let tr = tbl.rows[n+1];
if (!document.getElementById("bwapply_" + (n+1)).disabled)
continue;
console.log("Table Update row: " + i + " portNum is " + n + ", pState is " + pState[i-2] + ", row number is " + (n+1));
let iBW = parseInt(p.iBW,16) * 16; let eBW = parseInt(p.eBW,16) * 16;
document.getElementById("ilimit_port_" + (n+1)).checked = p.iLimited;
document.getElementById("elimit_port_" + (n+1)).checked = p.eLimited;
if (!p.iLimited) {
tr.cells[2].innerHTML = t('bw_unlimited');
} else {
tr.cells[2].innerHTML = '<input id="ibw_' + (n+1) + iLayout + (n+1) + ')" value="' + iBW +'"/>';
}
if (!p.eLimited) {
tr.cells[5].innerHTML = t('bw_unlimited');
} else {
tr.cells[5].innerHTML = '<input id="ebw_' + (n+1) + iLayout + (n+1) + ')" value="' + eBW +'"/>';
}
document.getElementById("fc_port_" + (n+1)).checked = p.iFC==1?true:false;
document.getElementById("fc_port_" + (n+1)).disabled = p.iLimited==1?false:true;
}
}
}
};
xhttp.open("GET", "/bandwidth.json", true);
xhttp.timeout = 1500; sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
update( () => {
createBW();
getBW();
const interval = setInterval(update, 2000);
});
});
-138
View File
@@ -1,138 +0,0 @@
var configInterval = Number();
var configuration = [];
const conf_cmds = [
/^ip\s+(\d{1,3}\.){3}\d{1,3}$/,
/^ip\s+dhcp$/,
/^gw\s+(\d{1,3}\.){3}\d{1,3}$/,
/^netmask\s+(\d{1,3}\.){3}\d{1,3}$/,
/^syslog\s+(on|off)$/,
/^syslog\s+ip\s+(\d{1,3}\.){3}\d{1,3}$/,
/^syslog\s+port\s+\d{1,5}$/,
/^passwd\s+\S+$/,
/^vlan\s+\d{1,4}\s+d$/,
/^vlan\s+\d{1,4}\s+mgmt$/,
/^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+[1-9]t?)+$/,
/^pvid\s+\d{1,2}\s+\d{1,4}$/,
/^ingress(\s+\d{1,2}[tua])+$/,
/^ingress\s+[tua]$/,
/^port\s+\d{1,2}\s+(10m|100m|1g|2g5|5g|10g|auto|on|off)(\s+(half|full))?$/,
/^port\s+\d{1,2}\s+name\s+\S+$/,
/^eee(\s+\d{1,2})?\s+(on|off)$/,
/^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,
/^lag\s+\d(\s+\d{1,2})+$/,
/^laghash\s+\d(\s+\w+)+$/,
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
/^stp\s+(on|off)$/,
/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/,
/^stp\s+version\s+(rstp|stp)$/,
/^stp\s+port\s+\d{1,2}\s+(on|off)$/,
/^stp\s+port\s+\d{1,2}\s+edge\s+(on|off|auto)$/,
/^stp\s+port\s+\d{1,2}\s+cost\s+\d{1,9}$/,
/^stp\s+port\s+\d{1,2}\s+prio\s+\d{1,3}$/,
/^stp\s+port\s+\d{1,2}\s+guard\s+(none|bpdu|root)$/,
/^stp\s+port\s+\d{1,2}\s+filter\s+(on|off)$/,
/^stp\s+port\s+\d{1,2}\s+p2p\s+(auto|on|off)$/,
/^igmp\s+(on|off)$/,
/^mtu\s+\d{1,2}\s+\d+$/,
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
/^hostname\s+.{1,23}$/,
];
/* Commands that come in an on/off pair replace each other, which the list
* below cannot express: it drops lines starting with the text it matched, and
* "syslog off" does not start with "syslog on". Naming the stem separately
* keeps the pair collapsed without widening the match to the whole family. */
const conf_toggle = [
/^(syslog)\s+(?:on|off)$/,
];
const conf_overwrite = [
/^ip\b/,
/^gw\b/,
/^netmask\b/,
/^syslog\s+ip\b/,
/^syslog\s+port\b/,
/^syslog\s+(on|off)$/,
/^passwd\b/,
/^vlan\s+\d{1,4}\s+mgmt$/,
/^vlan\s+\d{1,4}(?!\s+mgmt\b)/,
/^pvid\s+\d{1,2}\b/,
/^ingress\b/,
/^port\s+\d{1,2}(?!\s+name\b)/,
/^port\s+\d{1,2}\s+name\b/,
/^eee\s+\d{1,2}\b/,
/^eee\b/,
/^mirror\b/,
/^lag\s+\d+\b/,
/^laghash\b/,
/^isolate\s+\d{1,2}\b/,
/^stp\s+(prio|hello|maxage|fwd|txhold|version)\b/,
/^stp\s+port\s+\d{1,2}\s+(edge|cost|prio|guard|filter|p2p)\b/,
/^igmp\b/,
/^mtu\s+\d{1,2}\b/,
/^bw\s+(in|out)\s+\d{1,2}\b/,
/^hostname\b/,
];
function parseConf(s){
var a = s.split(/\r\n|\n/);
for (var l = 0; l < a.length; l++) {
var line = a[l].trim().replace(/\s+/g, ' ');
if (!line.length) continue;
const deleteMatch = line.match(/^vlan\s+(\d{1,4})\s+d$/);
if (deleteMatch) {
const prefix = "vlan " + deleteMatch[1] + " ";
configuration = configuration.filter(c => !c.startsWith(prefix));
continue;
}
console.log(l + ' --> ' + line);
var ignore = true;
for (const x of conf_cmds)
if (x.test(line)) { ignore = false; break; }
if (ignore) continue;
for (const x of conf_toggle) {
const t = line.match(x);
if (t) {
configuration = configuration.filter(item => item !== t[1] + " on" && item !== t[1] + " off");
break;
}
}
for (const x of conf_overwrite) {
if (x.test(line)) {
let m = line.match(x);
let matchStr = m[0];
configuration = configuration.filter(item =>
!(item === matchStr || (item.startsWith(matchStr + " ") && !item.endsWith(" mgmt") && !item.startsWith(matchStr + " name "))));
break;
}
}
// Only one management VLAN can be active, so drop any previous mgmt entry
if (/^vlan\s+\d{1,4}\s+mgmt$/.test(line))
configuration = configuration.filter(item => !/^vlan\s+\d{1,4}\s+mgmt$/.test(item));
configuration.push(line);
}
console.log("Configuration now:");
for (const x of configuration) { console.log(x); }
}
async function fetchConfig() {
try {
const response = await fetch('/config');
console.log("CONFIG: ", response);
const t = await response.text();
return t;
} catch(err) {
console.error("Error: ", err);
}
}
async function fetchCmdLog() {
try {
const response = await fetch('/cmd_log');
console.log("CMD-Log: ", response);
const t = await response.text();
return t;
} catch(err) {
console.error("Error: ", err);
return "";
}
}
-26
View File
@@ -1,26 +0,0 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="eee_title">EEE Configuration</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<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">
</div>
<script src="/eee.js"></script>
<script src="/eee_sub.js"></script>
</div>
<script src="/navigation.js"></script>
</body>
</html>
-52
View File
@@ -1,52 +0,0 @@
function createEEE() {
var tbl = document.getElementById('eeetable');
if (tbl.rows.length <= 2 && numPorts) {
console.log("CREATING TABLE ", tbl.rows.length);
for (let i = 2; i < 2 + numPorts; i++) {
console.log("Table row: " + i + "pState: " + pState[i-2]);
const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i-1)));
for (let j = 0; j < 7; j++) {
td = tr.insertCell(); td.appendChild(document.createTextNode(" "));
}
}
}
}
function getEEE() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
console.log("EEE: ", JSON.stringify(s));
var tbl = document.getElementById('eeetable');
if (tbl.rows.length > 2 && numPorts) {
for (let i = 2; i < 2 + numPorts; i++) {
p = s[i-2];
let n = p.portNum;
console.log("Table Update row: " + i + " portNum is " + n + ", pState is " + pState[i-2]);
let tr = tbl.rows[n+1];
if (!p.isSFP) {
let eee = parseInt(p.eee,2); let lp = parseInt(p.eee_lp,2);
tr.cells[1].innerHTML = `${eee&4?t('eee_on'):t('eee_off')}`; tr.cells[2].innerHTML = `${eee&2?t('eee_on'):t('eee_off')}`; tr.cells[3].innerHTML = `${eee&1?t('eee_on'):t('eee_off')}`;
tr.cells[4].innerHTML = `${lp&4?t('eee_on'):t('eee_off')}`; tr.cells[5].innerHTML = `${lp&2?t('eee_on'):t('eee_off')}`; tr.cells[6].innerHTML = `${lp&1?t('eee_on'):t('eee_off')}`;
tr.cells[7].innerHTML = `${p.active}`;
tr.classList.toggle('disabled', pState[i-2] < 0); tr.classList.toggle('isNOK', !p.active); tr.classList.toggle('isOK', p.active);
}
tr.classList.toggle('isSFP', p.isSFP);
}
}
}
};
xhttp.open("GET", "/eee.json", true);
xhttp.timeout = 1500; sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
update( () => {
createEEE();
getEEE();
const interval = setInterval(update, 2000);
const iCount = setInterval(getEEE, 2000);
});
});
-18
View File
@@ -1,18 +0,0 @@
async function eeeSub(port, enable) {
var cmd = "eee ";
if (enable)
cmd = cmd + "on";
else
cmd = cmd + "off";
console.log("eeeSub port " + port, ", value " + enable);
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
console.log('Completed!', response);
} catch(err) {
console.error(`Error: ${err}`);
}
}
-609
View File
@@ -1,609 +0,0 @@
var LANG = {
en: {
nav_overview: 'Overview',
nav_port_config: 'Port Configuration',
nav_port_stat: 'Port Statistics',
nav_l2: 'L2 Configuration',
nav_mirror: 'Mirroring',
nav_lag: 'Link Aggregation',
nav_eee: 'EEE',
nav_bandwidth: 'Bandwidth Limits',
nav_system: 'System Settings',
nav_fw_update: 'Firmware Update',
port_name: 'Name',
port_status: 'Status',
port_not_enabled: 'Not enabled.',
port_link_speed: 'Link speed',
port_vendor: 'Vendor',
port_model: 'Model',
port_serial: 'Serial',
port_temp: 'Temp',
port_vcc: 'Vcc',
port_tx_fault: 'TX-Fault',
port_tx_disabled: 'TX-Disabled',
port_tx_bias: 'TX-Bias',
port_tx_power: 'TX-Power',
port_rx_power: 'RX-Power',
port_rx_los: 'RX-LOS',
speed_disabled: 'Disabled',
speed_down: 'Down',
port_title: 'FreeSwitchOS Port Configuration',
port_heading: 'Port Configuration',
port_col_port: 'Port',
port_col_name: 'Name',
port_col_speed: 'Current Link Speed',
port_col_set_speed: 'Set Speed',
port_col_disabled: 'Disabled',
port_col_devices: 'Connected devices',
port_devices: 'devices',
port_col_apply: 'Apply',
port_mtu_heading: 'Configure Maximum Frame Size (MTU) forwarded at Port',
port_auto: 'Auto',
port_2500m: '2500MBit/Full',
port_1000m: '1000MBit/Full',
port_100m_f: '100MBit/Full',
port_100m_h: '100MBit/Half',
port_10m_f: '10MBit/Full',
port_10m_h: '10MBit/Half',
port_apply: 'Apply',
stat_title: 'FreeSwitchOS Port Statistics',
stat_heading: 'Port Statistics',
stat_detailed: 'Detailed Port Statistics',
stat_close: 'Close',
stat_col_port: 'Port',
stat_col_name: 'Name',
stat_col_link: 'link',
stat_col_tx_good: 'TX Good',
stat_col_tx_bad: 'TX Bad',
stat_col_rx_good: 'RX Good',
stat_col_rx_bad: 'RX Bad',
stat_col_all: 'All Counters',
stat_counter: 'Counter',
stat_value: 'Value',
stat_show: 'Show',
vlan_title: 'FreeSwitchOS VLAN Configuration',
vlan_heading: 'VLAN Configuration',
vlan_select: 'VLAN Select:',
vlan_choose: '— VLAN Choose —',
vlan_id: 'VLAN ID:',
vlan_get_config: 'Get Configuration',
vlan_name: 'VLAN Name:',
vlan_tagged: 'Tagged Ports',
vlan_untagged: 'Untagged Ports',
vlan_select_all: 'Select all',
vlan_pvid: 'Use as default VLAN for incoming traffic (PVID)',
vlan_update: 'Update / Create',
vlan_configured: 'Configured VLANs',
vlan_col_name: 'Name',
vlan_col_member: 'Member Ports',
vlan_col_tagged: 'Tagged Ports',
vlan_col_untagged: 'Untagged Ports',
vlan_col_pvid: 'PVID Ports',
vlan_col_delete: 'Delete',
vlan_set_id_first: 'Set VLAN ID first',
vlan_delete_confirm: 'Delete VLAN ',
lag_title: 'Link Aggregation Configuration',
lag_heading: 'Link Aggregation Groups Configuration',
lag_update: 'Update / Create',
mirror_title: 'Mirror Configuration',
mirror_heading: 'Mirror Configuration',
mirror_enabled: 'Enabled:',
mirror_port: 'Mirroring Port:',
mirror_tx: 'Mirrored Ports (TX)',
mirror_rx: 'Mirrored Ports (RX)',
mirror_update: 'Update / Create',
mirror_disable: 'Disable Mirroring',
mirror_set_port_first: 'Set Mirroring Port first',
mirror_select_ports: 'Select Mirrored Ports',
eee_title: 'EEE Configuration',
eee_heading: 'EEE Status',
eee_advertising: 'Advertising',
eee_partner: 'Link-Partner advertises',
eee_port: 'Port',
eee_active: 'Active?',
eee_enable: 'Enable EEE',
eee_disable: 'Disable EEE',
eee_on: 'ON',
eee_off: 'OFF',
l2_title: 'FreeSwitchOS L2 Configuration',
l2_heading: 'L2 Configuration',
l2_col_port: 'Port',
l2_col_type: 'Type',
l2_col_remove: 'Remove Entry',
l2_shown: 'Shown:',
l2_delete: 'Delete',
l2_static: 'static',
l2_learned: 'learned',
bw_title: 'Ingress and Egress Bandwidth',
bw_heading: 'Ingress and Egress Bandwidth',
bw_ingress: 'Ingress',
bw_egress: 'Egress',
bw_col_port: 'Port',
bw_col_limit: 'Limit',
bw_col_bandwidth: 'Bandwidth [kBit/s]',
bw_col_flow: 'Flow Control',
bw_col_apply: 'Apply',
bw_unlimited: 'UNLIMITED',
sys_title: 'System Settings',
sys_tab_system: 'System',
sys_tab_advanced: 'Advanced',
sys_tab_console: 'Console',
sys_heading: 'System Settings',
sys_ip: 'IP address:',
sys_model: 'Model:',
sys_hostname: 'Hostname:',
sys_apply: 'Apply',
sys_netmask: 'Netmask:',
sys_gateway: 'Gateway:',
sys_language: 'Language:',
sys_mgmt_vlan: 'Management VLAN:',
sys_mgmt_untagged: 'untagged',
sys_mgmt_confirm: 'Move switch management to VLAN ',
sys_mgmt_warn: 'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
sys_ip_note: 'When updating the above settings, remember to point your browser to the new IP afterwards:',
sys_update: 'Update Settings',
sys_save_label: 'Save all current settings to Flash:',
sys_save: 'Save Settings to Flash',
sys_advanced: 'Advanced Settings',
sys_startup_config: 'Startup configuration:',
sys_startup_warn: 'Be careful when saving the directly edited startup configuration, you can lock yourself out:',
sys_clear_config: 'Clear Startup Config',
sys_save_startup: 'Save Startup Settings to Flash',
sys_reset: 'Reset Switch',
sys_console: 'Console Command',
sys_enter_cmd: 'Enter command:',
sys_send_cmd: 'Send Command',
sys_console_warn: 'Be careful when entering console commands, you can lock yourself out!',
sys_invalid_ip: 'Invalid ip:',
sys_reset_confirm: 'Are you sure you want to reset the switch?',
sys_resetting: 'Switch is resetting. Please wait and refresh the page.',
login_title: 'RTL Switch Login',
login_heading: 'RTL Switch Login',
login_wrong: 'Wrong password!',
login_password: 'Password',
login_login: 'Login',
index_title: 'FreeSwitchOS Main Page',
index_heading: 'Switch Configuration',
index_settings: 'Settings',
update_title: 'Firmware update',
update_heading: 'Firmware Update',
update_instruction: 'Choose a firmware update file to upload:',
update_upload: 'Upload File',
common_port: 'Port ',
common_pkts: ' pkts',
},
ja: {
nav_overview: '概要',
nav_port_config: 'ポート設定',
nav_port_stat: 'ポート統計',
nav_l2: 'L2 設定',
nav_mirror: 'ミラーリング',
nav_lag: 'リンクアグリゲーション',
nav_eee: 'EEE',
nav_bandwidth: '帯域制限',
nav_system: 'システム設定',
nav_fw_update: 'ファームウェア更新',
port_name: '名前',
port_status: '状態',
port_not_enabled: '無効',
port_link_speed: 'リンク速度',
port_vendor: 'ベンダー',
port_model: 'モデル',
port_serial: 'シリアル',
port_temp: '温度',
port_vcc: '電圧',
port_tx_fault: 'TX 障害',
port_tx_disabled: 'TX 無効',
port_tx_bias: 'TX バイアス',
port_tx_power: 'TX 電力',
port_rx_power: 'RX 電力',
port_rx_los: 'RX 信号ロス',
speed_disabled: '無効',
speed_down: 'リンクダウン',
port_title: 'FreeSwitchOS ポート設定',
port_heading: 'ポート設定',
port_col_port: 'ポート',
port_col_name: '名前',
port_col_speed: '現在のリンク速度',
port_col_set_speed: '速度設定',
port_col_disabled: '無効',
port_col_devices: '接続デバイス',
port_devices: 'デバイス',
port_col_apply: '適用',
port_mtu_heading: 'ポートの最大フレームサイズ (MTU) 設定',
port_auto: '自動',
port_2500m: '2500Mbps/全二重',
port_1000m: '1000Mbps/全二重',
port_100m_f: '100Mbps/全二重',
port_100m_h: '100Mbps/半二重',
port_10m_f: '10Mbps/全二重',
port_10m_h: '10Mbps/半二重',
port_apply: '適用',
stat_title: 'FreeSwitchOS ポート統計',
stat_heading: 'ポート統計',
stat_detailed: '詳細ポート統計',
stat_close: '閉じる',
stat_col_port: 'ポート',
stat_col_name: '名前',
stat_col_link: 'リンク',
stat_col_tx_good: 'TX 正常',
stat_col_tx_bad: 'TX 異常',
stat_col_rx_good: 'RX 正常',
stat_col_rx_bad: 'RX 異常',
stat_col_all: '全カウンタ',
stat_counter: 'カウンタ',
stat_value: '値',
stat_show: '表示',
vlan_title: 'FreeSwitchOS VLAN 設定',
vlan_heading: 'VLAN 設定',
vlan_select: 'VLAN 選択:',
vlan_choose: '— VLAN 選択 —',
vlan_id: 'VLAN ID:',
vlan_get_config: '設定取得',
vlan_name: 'VLAN 名:',
vlan_tagged: 'タグ付きポート',
vlan_untagged: 'タグ無しポート',
vlan_select_all: 'すべて選択',
vlan_pvid: '受信トラフィックのデフォルト VLAN (PVID)',
vlan_update: '更新 / 作成',
vlan_configured: '設定済み VLAN',
vlan_col_name: '名前',
vlan_col_member: 'メンバーポート',
vlan_col_tagged: 'タグ付きポート',
vlan_col_untagged: 'タグ無しポート',
vlan_col_pvid: 'PVID ポート',
vlan_col_delete: '削除',
vlan_set_id_first: 'VLAN ID を先に設定してください',
vlan_delete_confirm: 'VLAN 削除 ',
lag_title: 'リンクアグリゲーション設定',
lag_heading: 'リンクアグリゲーショングループ設定',
lag_update: '更新 / 作成',
mirror_title: 'ミラーリング設定',
mirror_heading: 'ミラーリング設定',
mirror_enabled: '有効:',
mirror_port: 'ミラーポート:',
mirror_tx: 'ミラー元ポート (TX)',
mirror_rx: 'ミラー元ポート (RX)',
mirror_update: '更新 / 作成',
mirror_disable: 'ミラーリング無効化',
mirror_set_port_first: 'ミラーポートを先に設定してください',
mirror_select_ports: 'ミラー元ポートを選択してください',
eee_title: 'EEE 設定',
eee_heading: 'EEE 状態',
eee_advertising: 'EEE アドバタイジング',
eee_partner: 'リンクパートナー広告',
eee_port: 'ポート',
eee_active: '有効?',
eee_enable: 'EEE 有効化',
eee_disable: 'EEE 無効化',
eee_on: 'オン',
eee_off: 'オフ',
l2_title: 'FreeSwitchOS L2 設定',
l2_heading: 'L2 設定',
l2_col_port: 'ポート',
l2_col_type: 'タイプ',
l2_col_remove: 'エントリ削除',
l2_shown: 'Shown:',
l2_delete: '削除',
l2_static: '静的',
l2_learned: '学習',
bw_title: '入力/出力帯域制限',
bw_heading: '入力/出力帯域制限',
bw_ingress: '入力',
bw_egress: '出力',
bw_col_port: 'ポート',
bw_col_limit: '制限',
bw_col_bandwidth: '帯域 [kbps]',
bw_col_flow: 'フロー制御',
bw_col_apply: '適用',
bw_unlimited: '制限無し',
sys_title: 'システム設定',
sys_tab_system: 'システム',
sys_tab_advanced: '詳細設定',
sys_tab_console: 'コンソール',
sys_heading: 'システム設定',
sys_ip: 'IP アドレス:',
sys_model: 'モデル:',
sys_hostname: 'ホスト名:',
sys_apply: '適用',
sys_netmask: 'ネットマスク:',
sys_gateway: 'ゲートウェイ:',
sys_language: '言語:',
sys_mgmt_vlan: 'Management VLAN:',
sys_mgmt_untagged: 'untagged',
sys_mgmt_confirm: 'Move switch management to VLAN ',
sys_mgmt_warn: 'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
sys_ip_note: '上記設定を変更した場合は、ブラウザで新しい IP にアクセスしてください:',
sys_update: '設定更新',
sys_save_label: '現在の設定をフラッシュに保存:',
sys_save: '設定をフラッシュに保存',
sys_advanced: '詳細設定',
sys_startup_config: '起動設定:',
sys_startup_warn: '起動設定を直接編集する際は注意してください。ロックアウトされる可能性があります:',
sys_clear_config: '起動設定クリア',
sys_save_startup: '起動設定をフラッシュに保存',
sys_reset: 'スイッチ再起動',
sys_console: 'コンソールコマンド',
sys_enter_cmd: 'コマンド入力:',
sys_send_cmd: 'コマンド送信',
sys_console_warn: 'コンソールコマンドは注意して入力してください。ロックアウトされる可能性があります!',
sys_invalid_ip: '無効な IP: ',
sys_reset_confirm: 'スイッチを再起動してもよろしいですか?',
sys_resetting: 'スイッチを再起動中です。しばらく待ってからページをリロードしてください。',
login_title: 'RTL スイッチ ログイン',
login_heading: 'RTL スイッチ ログイン',
login_wrong: 'パスワードが違います!',
login_password: 'パスワード',
login_login: 'ログイン',
index_title: 'FreeSwitchOS メインページ',
index_heading: 'スイッチ設定',
index_settings: '設定',
update_title: 'ファームウェア更新',
update_heading: 'ファームウェア更新',
update_instruction: 'アップロードするファームウェアファイルを選択:',
update_upload: 'ファイルをアップロード',
common_port: 'ポート ',
common_pkts: ' pkts',
},
zh: {
nav_overview: '概览',
nav_port_config: '端口配置',
nav_port_stat: '端口统计',
nav_l2: 'L2 配置',
nav_mirror: '端口镜像',
nav_lag: '链路聚合',
nav_eee: 'EEE',
nav_bandwidth: '带宽限制',
nav_system: '系统设置',
nav_fw_update: '固件升级',
port_name: '名称',
port_status: '状态',
port_not_enabled: '未启用。',
port_link_speed: '链路速率',
port_vendor: '厂商',
port_model: '型号',
port_serial: '序列号',
port_temp: '温度',
port_vcc: '供电电压',
port_tx_fault: 'TX 故障',
port_tx_disabled: 'TX 禁用',
port_tx_bias: 'TX 偏置电流',
port_tx_power: 'TX 光功率',
port_rx_power: 'RX 光功率',
port_rx_los: 'RX 信号丢失',
speed_disabled: '禁用',
speed_down: '未连接',
port_title: 'FreeSwitchOS 端口配置',
port_heading: '端口配置',
port_col_port: '端口',
port_col_name: '名称',
port_col_speed: '当前链路速率',
port_col_set_speed: '设置速率',
port_col_disabled: '禁用',
port_col_devices: '已连接设备',
port_devices: '台设备',
port_col_apply: '应用',
port_mtu_heading: '配置端口转发的最大帧大小 (MTU)',
port_auto: '自动',
port_2500m: '2500Mbps/全双工',
port_1000m: '1000Mbps/全双工',
port_100m_f: '100Mbps/全双工',
port_100m_h: '100Mbps/半双工',
port_10m_f: '10Mbps/全双工',
port_10m_h: '10Mbps/半双工',
port_apply: '应用',
stat_title: 'FreeSwitchOS 端口统计',
stat_heading: '端口统计',
stat_detailed: '详细端口统计',
stat_close: '关闭',
stat_col_port: '端口',
stat_col_name: '名称',
stat_col_link: '链路',
stat_col_tx_good: 'TX 正常包',
stat_col_tx_bad: 'TX 错误包',
stat_col_rx_good: 'RX 正常包',
stat_col_rx_bad: 'RX 错误包',
stat_col_all: '所有计数器',
stat_counter: '计数器',
stat_value: '值',
stat_show: '查看',
vlan_title: 'FreeSwitchOS VLAN 配置',
vlan_heading: 'VLAN 配置',
vlan_select: 'VLAN 选择:',
vlan_choose: '-- 请选择 VLAN --',
vlan_id: 'VLAN ID:',
vlan_get_config: '获取配置',
vlan_name: 'VLAN 名称:',
vlan_tagged: 'Tagged 端口',
vlan_untagged: 'Untagged 端口',
vlan_select_all: '全选',
vlan_pvid: '作为入方向流量的默认 VLAN (PVID)',
vlan_update: '更新 / 创建',
vlan_configured: '已配置 VLAN',
vlan_col_name: '名称',
vlan_col_member: '成员端口',
vlan_col_tagged: 'Tagged 端口',
vlan_col_untagged: 'Untagged 端口',
vlan_col_pvid: 'PVID 端口',
vlan_col_delete: '删除',
vlan_set_id_first: '请先设置 VLAN ID',
vlan_delete_confirm: '删除 VLAN ',
lag_title: '链路聚合配置',
lag_heading: '链路聚合组配置',
lag_update: '更新 / 创建',
mirror_title: '端口镜像配置',
mirror_heading: '端口镜像配置',
mirror_enabled: '启用:',
mirror_port: '镜像目的端口:',
mirror_tx: '被镜像端口 (TX)',
mirror_rx: '被镜像端口 (RX)',
mirror_update: '更新 / 创建',
mirror_disable: '禁用端口镜像',
mirror_set_port_first: '请先设置镜像目的端口',
mirror_select_ports: '请选择被镜像端口',
eee_title: 'EEE 配置',
eee_heading: 'EEE 状态',
eee_advertising: '本端通告',
eee_partner: '链路伙伴通告',
eee_port: '端口',
eee_active: '已生效?',
eee_enable: '启用 EEE',
eee_disable: '禁用 EEE',
eee_on: '开',
eee_off: '关',
l2_title: 'FreeSwitchOS L2 配置',
l2_heading: 'L2 配置',
l2_col_port: '端口',
l2_col_type: '类型',
l2_col_remove: '删除条目',
l2_shown: 'Shown:',
l2_delete: '删除',
l2_static: '静态',
l2_learned: '动态学习',
bw_title: '入方向/出方向带宽限制',
bw_heading: '入方向/出方向带宽限制',
bw_ingress: '入方向',
bw_egress: '出方向',
bw_col_port: '端口',
bw_col_limit: '限速',
bw_col_bandwidth: '带宽 [kbit/s]',
bw_col_flow: '流量控制',
bw_col_apply: '应用',
bw_unlimited: '不限速',
sys_title: '系统设置',
sys_tab_system: '系统',
sys_tab_advanced: '高级',
sys_tab_console: '控制台',
sys_heading: '系统设置',
sys_ip: 'IP 地址:',
sys_model: '型号:',
sys_hostname: '主机名:',
sys_apply: '应用',
sys_netmask: '子网掩码:',
sys_gateway: '网关:',
sys_language: '语言:',
sys_mgmt_vlan: 'Management VLAN:',
sys_mgmt_untagged: 'untagged',
sys_mgmt_confirm: 'Move switch management to VLAN ',
sys_mgmt_warn: 'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
sys_ip_note: '更新上述设置后,请使用新的 IP 地址重新访问管理界面:',
sys_update: '更新设置',
sys_save_label: '将当前全部设置保存到 Flash:',
sys_save: '保存设置到 Flash',
sys_advanced: '高级设置',
sys_startup_config: '启动配置:',
sys_startup_warn: '直接编辑启动配置时请谨慎,错误配置可能导致无法访问设备:',
sys_clear_config: '清除启动配置',
sys_save_startup: '保存启动配置到 Flash',
sys_reset: '重启交换机',
sys_console: '控制台命令',
sys_enter_cmd: '输入命令:',
sys_send_cmd: '发送命令',
sys_console_warn: '输入控制台命令时请谨慎,错误命令可能导致无法访问设备!',
sys_invalid_ip: '无效 IP: ',
sys_reset_confirm: '确定要重启交换机吗?',
sys_resetting: '交换机正在重启。请稍候并刷新页面。',
login_title: 'RTL 交换机登录',
login_heading: 'RTL 交换机登录',
login_wrong: '密码错误!',
login_password: '密码',
login_login: '登录',
index_title: 'FreeSwitchOS 主页',
index_heading: '交换机配置',
index_settings: '设置',
update_title: '固件升级',
update_heading: '固件升级',
update_instruction: '请选择要上传的固件文件:',
update_upload: '上传文件',
common_port: '端口 ',
common_pkts: ' 个包',
}
};
var rtlLang = (function() {
var saved = localStorage.getItem('rtl_lang');
if (saved && LANG[saved]) return saved;
var browser = (navigator.language || navigator.userLanguage || 'en').substring(0, 2);
return LANG[browser] ? browser : 'en';
})();
function t(key) {
return LANG[rtlLang][key] || LANG['en'][key] || key;
}
function setLang(lang) {
if (LANG[lang]) {
localStorage.setItem('rtl_lang', lang);
rtlLang = lang;
document.querySelectorAll('[data-i18n]').forEach(function(el) {
applyTranslation(el);
});
}
}
function applyTranslation(el) {
var key = el.getAttribute('data-i18n');
if (!key) return;
if (el.tagName === 'INPUT' && (el.type === 'submit' || el.type === 'button')) {
el.value = t(key);
} else if (el.tagName === 'OPTION') {
el.textContent = t(key);
} else if (el.tagName === 'TITLE') {
el.textContent = t(key);
} else {
el.innerHTML = t(key);
}
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('[data-i18n]').forEach(function(el) {
applyTranslation(el);
});
});
+477 -25
View File
@@ -1,32 +1,484 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/main_info.js"></script>
<script src="/i18n.js"></script>
<script>
window.addEventListener("load", function() {
update( () => {
const interval = setInterval(update, 2000);
});
});
</script>
<link rel="stylesheet" href="style.css">
<title data-i18n="index_title">FreeSwitchOS Main Page</title>
<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>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<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>
<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>
<script src="/navigation.js"></script>
<ul id="navlist"></ul>
<div id="navfoot"><span id="fver"></span></div>
</nav>
<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>
<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>
</section>
<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>
</section>
<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 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="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="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="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="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></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>
<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>
</div>
<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>
<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>
</section>
</main>
</div>
<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>
-30
View File
@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="l2_title">FreeSwitchOS L2 Configuration</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<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>
<script src="/l2.js"></script>
</table>
</div>
</body>
<script src="/navigation.js"></script>
</html>
-178
View File
@@ -1,178 +0,0 @@
function fillStats() {
var tbl = document.getElementById('statstable');
if (!numPorts)
return;
if (tbl.rows.length > 1) {
for (let i = 0; i < numPorts; i++) {
console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] +1]);
tbl.rows[i+1].cells[1].innerHTML = linkText(pState[i]+1);
tbl.rows[i+1].cells[2].innerHTML = `${txG[i]}` + t('common_pkts');
tbl.rows[i+1].cells[3].innerHTML = `${txB[i]}` + t('common_pkts');
tbl.rows[i+1].cells[4].innerHTML = `${rxG[i]}` + t('common_pkts');
tbl.rows[i+1].cells[5].innerHTML = `${rxB[i]}` + t('common_pkts');
}
} else {
for (let i = 0; i < numPorts; i++) {
console.log("Table row: " + i);
const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i+1)));
td = tr.insertCell(); td.appendChild(document.createTextNode(linkText(pState[i]+1)));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]}` + t('common_pkts')));
td = tr.insertCell();td.appendChild(document.createTextNode(`${txB[i]}` + t('common_pkts')));
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxG[i]}` + t('common_pkts')));
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxB[i]}` + t('common_pkts')));
}
}
}
function l2CMP(a, b)
{
if (a.port < b.port)
return -1;
if (a.port > b.port)
return 1;
if (a.mac < b.mac)
return -1;
if (a.mac > b.mac)
return 1;
if (a.vlan < b.vlan)
return -1;
if (a.vlan > b.vlan)
return 1;
return 0;
}
function uniq(a) {
return a.filter(function(item, pos, ary) {
return !pos || item.idx != ary[pos - 1].idx;
});
}
function delL2(idx) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
if (JSON.parse(xhttp.responseText).result == 1) {
l2All = l2All.filter(function(e) { return e.idx != idx; });
renderL2();
}
}
};
xhttp.open("GET", "/l2_del.json?idx=" + idx, true);
xhttp.timeout = 1500; xhttp.send();
}
var l2All = [];
const l2Cols = ['port', 'mac', 'vlan', 'type'];
var l2SortCol = 'port';
var l2SortDir = 1;
function l2Key(e, col) {
if (col === 'port') {
if (e.port === 'CPU') return Number.MAX_SAFE_INTEGER;
if (e.lag) return 100 + e.lag;
return Number(e.port);
}
if (col === 'vlan') return Number(e.vlan);
return String(e[col]).toLowerCase();
}
function l2SortBy(col) {
l2SortDir = (col === l2SortCol) ? -l2SortDir : 1;
l2SortCol = col;
renderL2();
}
function l2FilterChanged() { renderL2(); }
function renderL2() {
var tbl = document.getElementById('l2table');
if (!tbl) return;
var f = {};
l2Cols.forEach(function(c) {
var el = document.getElementById('l2f_' + c);
f[c] = el ? el.value.trim().toLowerCase() : '';
});
var rows = l2All.filter(function(e) {
return l2Cols.every(function(c) {
return !f[c] || String(e[c]).toLowerCase().indexOf(f[c]) !== -1;
});
});
rows.sort(function(a, b) {
var x = l2Key(a, l2SortCol), y = l2Key(b, l2SortCol);
return (x < y ? -1 : x > y ? 1 : 0) * l2SortDir;
});
l2Cols.forEach(function(c) {
var a = document.getElementById('l2a_' + c);
if (a) a.textContent = (c === l2SortCol) ? (l2SortDir > 0 ? ' \u25b2' : ' \u25bc') : ' \u21c5';
});
paintL2(tbl, rows);
var cnt = document.getElementById('l2count');
if (cnt) cnt.textContent = rows.length + ' / ' + l2All.length;
}
function fillL2(s)
{
var tbl = document.getElementById('l2table');
if (!s.length)
return;
s.sort(l2CMP);
s = uniq(s);
l2All = s.map(function(e) {
if (e.lag)
e.port = 'LAG' + e.lag;
return e;
});
renderL2();
}
function delL2Button(e)
{
if (e.port == 'CPU')
return '';
return '<button type="button" onclick="delL2(' + e.idx + ');">' + t('l2_delete') + '</button>';
}
function paintL2(tbl, s)
{
console.log("L2: ", JSON.stringify(s));
for (let i = 0; i < s.length; i++) {
var e = s[i];
console.log(i, e);
if (tbl.rows[i+1]) {
tbl.rows[i+1].cells[0].innerHTML = `${e.port}`;
tbl.rows[i+1].cells[1].innerHTML = `${e.mac}`;
tbl.rows[i+1].cells[2].innerHTML = `${e.vlan}`;
tbl.rows[i+1].cells[3].innerHTML = `${e.type}`;
tbl.rows[i+1].cells[4].innerHTML = delL2Button(e);
} else {
const tr = tbl.insertRow();
let td = tr.insertCell(); td.innerHTML = `${e.port}`;
td = tr.insertCell(); td.innerHTML = `${e.mac}`;
td = tr.insertCell(); td.innerHTML = `${e.vlan}`;
td = tr.insertCell(); td.innerHTML = `${e.type}`;
td = tr.insertCell(); td.innerHTML = delL2Button(e);
}
}
for (let i = tbl.rows.length - 1; i > s.length; i--)
tbl.deleteRow(i);
}
function getL2() {
walkL2(function(entries, ok) {
if (ok) {
for (var i = 0; i < entries.length; i++)
entries[i].type = entries[i].type == "s" ? t('l2_static') : t('l2_learned');
fillL2(entries);
}
setTimeout(getL2, 1000);
});
}
window.addEventListener("load", function() {
update( () => {
getL2();
const interval = setInterval(update, 2000);
});;
});
-28
View File
@@ -1,28 +0,0 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="lag_title">Link Aggregation Configuration</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<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>
<script src="/lag.js"></script>
</div>
<script src="/navigation.js"></script>
</body>
</html>
-81
View File
@@ -1,81 +0,0 @@
var lagInterval = Number();
function lagForm() {
if (!numPorts)
return;
for (let j=0; j < 4; j++) {
var lag = "mLAG" + j
console.log("Adding LAG " + lag)
var m = document.getElementById(lag);
for (let i = 1; i <= numPorts; i++) {
const d = document.createElement("div");
d.classList.add("cbgroup");
const l = document.createElement("label");
l.innerHTML = "" + i;
l.classList.add("cbgroup");
const inp = document.createElement("input");
inp.type = "checkbox"; inp.setAttribute("class","psel");
inp.id = "p_" + lag + "_" + i;
const o = document.createElement("img");
if (pIsSFP[i - 1]) {
o.src = "sfp.svg"; o.width ="60"; o.height ="60";
} else {
o.src = "port.svg"; o.width = "40"; o.height = "40";
}
l.appendChild(inp); l.appendChild(o);
d.appendChild(l)
m.appendChild(d);
}
}
fetchLag();
}
function setL(p, c){
console.log("LAG setting: ", p, " to ", c);
document.getElementById(p).checked=c;
}
function fetchLag() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
console.log("LAG: ", JSON.stringify(s));
for (let l = 0; l < 4; l++) {
let members = parseInt(s[l].members, 2);
let hash = parseInt(s[l].hash, 16);
for (let i = 1; i <= numPorts; i++) {
let p = i - 1;
if (numPorts < 9)
p = physToLogPort[p];
setL("p_mLAG"+l+"_"+i, members & (1<<p));
}
}
}
};
xhttp.open("GET", `/lag.json`, true);
sendXHTTP(xhttp);
}
async function lagSub(l) {
var cmd = "lag " + (l + 1);
for (let i = 1; i <= numPorts; i++) {
if (document.getElementById("p_mLAG"+l+"_"+i).checked)
cmd = cmd + ` ${i}`;
}
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
console.log('Completed!', response);
} catch(err) {
console.error(`Error: ${err}`);
}
}
window.addEventListener("load", function() {
update( () => {
lagForm();
const interval = setInterval(update, 2000);
});
});
+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="/i18n.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>
-350
View File
@@ -1,350 +0,0 @@
var txG = new BigInt64Array(10);
var txB = new BigInt64Array(10);
var rxG = new BigInt64Array(10);
var rxB = new BigInt64Array(10);
const linkS = [function(){return t('speed_disabled')}, function(){return t('speed_down')}, "10M", "100M", "1000M", "500M", "10G", "2.5G", "5G"];
var pState = new Int8Array(10);
var pIsSFP = new Int8Array(10);
var pAdvertised = new Int8Array(10);
var numPorts = 0;
function linkText(idx) { var v = linkS[idx]; return typeof v === 'function' ? v() : v; }
var logToPhysPort = new Int8Array(10);
var physToLogPort = new Int8Array(10);
var portNames = new Array(10);
var currentRequests = [];
var currentCallback;
function drawPorts() {
var f = document.getElementById('ports');
console.log("DRAWING PORTS: ", numPorts);
for (let i = 0; i < numPorts; i++) {
console.log("DRAWING isSFP: ", pIsSFP[i]);
const d = document.createElement("div");
d.classList.add('tooltip');
const s = document.createElement("span");
s.classList.add("tooltiptext");
s.innerHTML = t('common_port');
s.id="tt_" + (i+1);
const l = document.createElement("object");
d.appendChild(l);
d.appendChild(s);
l.type = "image/svg+xml";
if (!pIsSFP[i]) {
l.data = "port.svg";
l.width ="40";
l.height ="40";
} else {
l.data = "sfp.svg";
l.width = "60";
l.height = "60";
}
l.id="port" + (i+1);
f.appendChild(d);
}
}
function parseUint16(val) {
return parseInt(val, 16) & 0xffff;
}
function parseInt16(val) {
let valInt = parseInt(val, 16);
let num = valInt & 0x7fff;
if (valInt & 0x8000) {
return num - 0x8000;
}
return num;
}
function applyCalibrationSlopeOffset(val, cal) {
if (typeof cal !== 'string') {
return val;
}
if (cal.startsWith("0x")) {
cal = cal.substring(2);
}
if (cal.length != 8) {
return val;
}
let slope = parseUint16(cal.substring(0, 4)) / 256;
let offset = parseInt16(cal.substring(4, 8));
return slope * val + offset;
}
function applyRxPowerCalibration(val, cal) {
if (typeof cal !== 'string') {
return val;
}
if (cal.startsWith("0x")) {
cal = cal.substring(2);
}
if (cal.length != 40) {
return val;
}
let bytes = cal.match(/.{1,2}/g).map(function (x) { return parseInt(x, 16); });
let view = new DataView(new Uint8Array(bytes).buffer);
return view.getFloat32(0) * Math.pow(val, 4)
+ view.getFloat32(4) * Math.pow(val, 3)
+ view.getFloat32(8) * Math.pow(val, 2)
+ view.getFloat32(12) * val
+ view.getFloat32(16);
}
function decodeSfpTemp(val, cal) {
let temp = parseInt16(val);
return applyCalibrationSlopeOffset(temp, cal) / 256;
}
function decodeSfpVcc(val, cal) {
let vcc = parseUint16(val);
return applyCalibrationSlopeOffset(vcc, cal) / 10000;
}
function decodeSfpTxBias(val, cal) {
let bias = parseUint16(val);
return applyCalibrationSlopeOffset(bias, cal) / 500;
}
function decodeSfpTxPower(val, cal) {
let txPower = parseUint16(val);
return applyCalibrationSlopeOffset(txPower, cal) / 10000;
}
function decodeSfpRxPower(val, cal) {
let rxPower = parseUint16(val);
return applyRxPowerCalibration(rxPower, cal) / 10000;
}
function convertPowerTodBm(val) {
return 10 * Math.log10(val);
}
function update(callback) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
console.log("IN UPDATE ");
if (this.readyState == 4 && this.status == 401)
document.location = "/login.html"
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
if (!numPorts) {
numPorts = s.length;
for (let i = 0; i < s.length; i++)
pIsSFP[s[i].portNum-1] = s[i].isSFP;
drawPorts();
}
console.log("RES:", JSON.stringify(s));
for (let i = 0; i < s.length; i++) {
p = s[i];
let n = p.portNum;
logToPhysPort[p.logPort] = n;
physToLogPort[n-1] = p.logPort;
portNames[p.logPort] = p.name;
let pid = "port" + n;
let ttid = "tt_" + n;
n--;
txG[n] = BigInt(p.txG); txB[n] = BigInt(p.txB); rxG[n] = BigInt(p.rxG); rxB[n] = BigInt(p.rxB);
var psvg = document.getElementById(pid);
var tt = document.getElementById(ttid);
if (psvg == null || !psvg.contentDocument)
continue;
var bgs = psvg.contentDocument.getElementsByClassName("bg");
var leds = psvg.contentDocument.getElementsByClassName("led");
if (leds[0] == null || leds[0].style == null)
continue;
const portName = p.name || portNames[p.logPort] || '';
var iHTML = "<table border=\"0\" class=\"tt_table\">";
if (portName) iHTML += "<tr><td align=\"left\">" + t('port_name') + "</td><td>:</td><td>" + portName + "</td></tr>";
if (p.enabled == 0) {
pState[n] = -1;
bgs[0].style.fill = "red";
leds[0].style.fill = "black"; leds[1].style.fill = "black";
psvg.style.opacity = 0.4;
iHTML += "<tr><td align=\"left\">" + t('port_status') + "</td><td>:</td><td>" + t('port_not_enabled') + "</td></tr>";
iHTML += "</table>";
tt.innerHTML = iHTML;
} else {
psvg.style.opacity = 1.0;
pState[n] = p.link;
if (p.link == 5 || p.link == 7) {
leds[0].style.fill = "green"; leds[1].style.fill = "blue";
} else if (p.link == 4 || p.link == 6) {
leds[0].style.fill = "green"; leds[1].style.fill = "orange";
} else if (p.link == 1 || p.link == 2 || p.link == 3) {
leds[0].style.fill = "green"; leds[1].style.fill = "green";
} else {
leds[0].style.fill = "black"; leds[1].style.fill = "black";
psvg.style.opacity = 0.4
}
iHTML += "<tr><td align=\"left\">" + t('port_link_speed') + "</td><td>:</td><td>" + linkText(p.link + 1) + "</td></tr>";
if (p.isSFP) {
pAdvertised[n] = 0;
const hasExtendedStatus = p.sfp_options & 0x40;
iHTML += "<tr><td>" + t('port_vendor') + "</td><td>:</td><td>" + p.sfp_vendor + "</td></tr>";
iHTML += "<tr><td>" + t('port_model') + "</td><td>:</td><td>" + p.sfp_model + "</td></tr>";
iHTML += "<tr><td>" + t('port_serial') + "</td><td>:</td><td>" + p.sfp_serial + "</td></tr>";
if (hasExtendedStatus) {
let txPower = decodeSfpTxPower(p.sfp_txpower, p.sfp_txpower_cal);
let txPowerdBm = convertPowerTodBm(txPower);
let rxPower = decodeSfpRxPower(p.sfp_rxpower, p.sfp_rxpower_cal);
let rxPowerdBm = convertPowerTodBm(rxPower);
iHTML += "<tr><td>" + t('port_temp') + "</td><td>:</td><td>" + decodeSfpTemp(p.sfp_temp, p.sfp_temp_cal).toFixed(2) + "&#8239;&#8451;</td></tr>";
iHTML += "<tr><td>" + t('port_vcc') + "</td><td>:</td><td>" + decodeSfpVcc(p.sfp_vcc, p.sfp_vcc_cal).toFixed(2) + "&#8239;V</td></tr>";
iHTML += "<tr><td>" + t('port_tx_fault') + "</td><td>:</td><td>" + (Boolean(Number(p.sfp_state) & 0x4)) + "</td></tr>";
iHTML += "<tr><td>" + t('port_tx_disabled') + "</td><td>:</td><td>" + (Boolean(Number(p.sfp_state) & 0x80)) + "</td></tr>";
iHTML += "<tr><td>" + t('port_tx_bias') + "</td><td>:</td><td>" + decodeSfpTxBias(p.sfp_txbias, p.sfp_txbias_cal).toFixed(1) + "&#8239;mA</td></tr>";
iHTML += "<tr><td>" + t('port_tx_power') + "</td><td>:</td><td>" + txPower.toFixed(3) + "&#8239;mW / " + txPowerdBm.toFixed(2) + "&#8239;dBm</td></tr>";
iHTML += "<tr><td>" + t('port_rx_power') + "</td><td>:</td><td>" + rxPower.toFixed(3) + "&#8239;mW / " + rxPowerdBm.toFixed(2) + "&#8239;dBm</td></tr>";
}
// Not all devices & modules have LOS pin...
const rx_los_pin = p.sfp_los !== null ? Boolean(Number(p.sfp_los)) : null;
const rx_los_module = hasExtendedStatus ? Boolean(Number(p.sfp_state) & 0x2) : null;
if (rx_los_module !== null || rx_los_pin !== null) {
iHTML += `<tr><td>` + t('port_rx_los') + `</td><td>:</td><td>${rxLosHTML(rx_los_pin, rx_los_module)}</td></tr>`;
}
} else {
pAdvertised[n] = parseInt(p.adv, 2);
};
iHTML += "</table>";
tt.innerHTML = iHTML;
}}
if (callback)
callback();
}};
xhttp.open("GET", "/status.json", true);
xhttp.timeout = 5000;
sendXHTTP(xhttp);
}
function rxLosHTML(pinStatus, moduleStatus) {
if (moduleStatus !== null && pinStatus !== null && moduleStatus !== pinStatus) {
return `pin=${pinStatus}<br/>mod=${moduleStatus}<br/>❗❗❗❗`;
}
// Returns first non null value
return moduleStatus ?? pinStatus;
}
function callbackXHTTP()
{
x = currentRequests.shift();
x.onreadystatechange = currentCallback;
x.onreadystatechange();
if (currentRequests.length === 0)
return;
x = currentRequests[0];
currentCallback = x.onreadystatechange;
x.onreadystatechange = callbackXHTTP;
var retries = 10;
while (retries) {
try {
setTimeout(() => {
x.send();
console.log("B1");
}, 20);
} catch (error) {
retries--;
setTimeout(() => {
console.log(`Retry ${retries}/${maxRetries} failed: ${error.message}`);
}, 200);
if (retries < 1) {
throw error;
}
}
console.log("B2");
return;
}
}
function sendXHTTP(x)
{
console.log("sendXHTTP ", x);
if (currentRequests.length === 0) {
currentRequests.push(x);
currentCallback = x.onreadystatechange;
x.onreadystatechange = callbackXHTTP;
var retries = 10;
while (retries) {
try {
x.send();
console.log("A1");
} catch (error) {
retries--;
setTimeout(() => {
console.log(`Retry ${retries}/${maxRetries} failed: ${error.message}`);
}, 200);
if (retries < 1) {
throw error;
}
}
console.log("A2");
return;
}
console.log("A3");
return;
}
currentRequests.push(x);
}
function walkL2(onDone)
{
var entries = [];
var idx = 0;
var tries = 0;
function retry() {
if (++tries < 3) {
setTimeout(page, 1000);
return;
}
onDone(entries, false);
}
function page() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState != 4)
return;
if (this.status != 200) {
retry();
return;
}
var s;
try {
s = JSON.parse(xhttp.responseText);
} catch (err) {
retry();
return;
}
tries = 0;
s = s.map(function(e) {
e.vlan = parseInt(e.vlan, 16);
e.idx = parseInt(e.idx, 16);
e.port = e.port == 9 ? 'CPU' : logToPhysPort[e.port];
return e;
});
if (!s.length) {
onDone(entries, true);
return;
}
entries.push(...s);
for (var i = entries.length - 1; i > 0; i--) {
if (entries[0].idx == entries[i].idx) {
onDone(entries, true);
return;
}
}
if (entries.length >= 4096) {
onDone(entries, true);
return;
}
idx = s[s.length - 1].idx + 1;
setTimeout(page, 1000);
};
xhttp.open("GET", "/l2.json?idx=" + idx, true);
xhttp.timeout = 1500;
sendXHTTP(xhttp);
}
page();
}
-22
View File
@@ -1,22 +0,0 @@
document.addEventListener("DOMContentLoaded", function () {
fetch('/information.json')
.then(response => response.json())
.then(data => {
const tableBody = document.getElementById('infoTable').querySelector('tbody');
// Create table rows
for (const [key, value] of Object.entries(data)) {
const row = document.createElement('tr');
const cellKey = document.createElement('td');
const cellValue = document.createElement('td');
cellKey.textContent = key;
cellValue.textContent = value;
row.appendChild(cellKey);
row.appendChild(cellValue);
tableBody.appendChild(row);
}
})
.catch(error => console.error('Error fetching the data:', error));
});
-27
View File
@@ -1,27 +0,0 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="mirror_title">Mirror Configuration</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<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">
<script src="/mirror.js"></script>
<script src="/mirror_sub.js"></script>
</div>
<script src="/navigation.js"></script>
</body>
</html>
-64
View File
@@ -1,64 +0,0 @@
var mirrorInterval = Number();
const mirrors = ["mPortsTX", "mPortsRX"];
function mirrorForm() {
if (!numPorts)
return;
for (let j=0; j < mirrors.length; j++) {
console.log("Adding Mirror " + j)
var m = document.getElementById(mirrors[j]);
for (let i = 1; i <= numPorts; i++) {
const d = document.createElement("div");
d.classList.add("cbgroup");
const l = document.createElement("label");
l.innerHTML = "" + i;
l.classList.add("cbgroup");
const inp = document.createElement("input");
inp.type = "checkbox"; inp.setAttribute("class","psel");
inp.id = mirrors[j] + i;
const o = document.createElement("img");
if (pIsSFP[i - 1]) {
o.src = "sfp.svg"; o.width ="60"; o.height ="60";
} else {
o.src = "port.svg"; o.width = "40"; o.height = "40";
}
l.appendChild(inp); l.appendChild(o);
d.appendChild(l)
m.appendChild(d);
}
}
fetchMirror();
}
function setM(p, c){
document.getElementById(p).checked=c;
}
function fetchMirror() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
console.log("MIRROR: ", JSON.stringify(s));
document.getElementById('me').checked = s.enabled;
document.getElementById('mp').value = s.mPort;
let m_tx = parseInt(s.mirror_tx, 2);
let m_rx = parseInt(s.mirror_rx, 2);
for (let i = 1; i <= numPorts; i++) {
let p = i - 1;
if (numPorts < 9)
p = physToLogPort[p];
setM("mPortsTX"+i, m_tx&(1<<p)); setM("mPortsRX"+i, m_rx&(1<<p));
}
}
};
xhttp.open("GET", `/mirror.json`, true);
sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
update( () => {
mirrorForm();
const interval = setInterval(update, 2000);
});
});
-43
View File
@@ -1,43 +0,0 @@
async function mirrorSub() {
var cmd = "mirror ";
var mp=document.getElementById('mp').value
if (!mp) {
alert(t('mirror_set_port_first'));
return;
}
document.getElementById(mirrors[0]+mp).checked=false;document.getElementById(mirrors[1]+mp).checked=false;
cmd = cmd + mp;
for (let i = 1; i <= numPorts; i++) {
if (document.getElementById(mirrors[0] + i).checked && document.getElementById(mirrors[1] + i).checked)
cmd = cmd + ` ${i}`;
else if (document.getElementById(mirrors[0] + i).checked)
cmd = cmd + ` ${i}t`;
else if (document.getElementById(mirrors[1] + i).checked)
cmd = cmd + ` ${i}r`;
}
if (cmd.length < 10) {
alert(t('mirror_select_ports'));
return;
}
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
console.log('Completed!', response);
} catch(err) {
console.error(`Error: ${err}`);
}
}
async function mirrorDel() {
var cmd = "mirror off";
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
location.reload();
} catch(err) {
console.error(`Error: ${err}`);
}
}
-21
View File
@@ -1,21 +0,0 @@
document.getElementById('sidebar').innerHTML =
"<ul><li><a href='index.html' data-i18n='nav_overview'>Overview</a></li>"
+ "<li><a href='ports.html' data-i18n='nav_port_config'>Port Configuration</a></li>"
+ "<li><a href='stat.html' data-i18n='nav_port_stat'>Port Statistics</a></li>"
+ "<li><a href='vlan.html' >VLAN</a></li>"
+ "<li><a href='l2.html' data-i18n='nav_l2'>L2 Configuration</a></li>"
+ "<li><a href='stp.html'>Spanning Tree</a></li>"
+ "<li><a href='mirror.html' data-i18n='nav_mirror'>Mirroring</a></li>"
+ "<li><a href='lag.html' data-i18n='nav_lag'>Link Aggregation</a></li>"
+ "<li><a href='eee.html' data-i18n='nav_eee'>EEE</a></li>"
+ "<li><a href='bandwidth.html' data-i18n='nav_bandwidth'>Bandwidth Limits</a></li>"
+ "<li><a href='system.html' data-i18n='nav_system'>System Settings</a></li>"
+ "<li><a href='update.html' data-i18n='nav_fw_update'>Firmware Update</a></li></ul>";
document.addEventListener('DOMContentLoaded', function() {
var links = document.querySelectorAll('#sidebar a[data-i18n]');
links.forEach(function(el) {
var key = el.getAttribute('data-i18n');
if (key) el.textContent = t(key);
});
});
-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

-25
View File
@@ -1,25 +0,0 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="port_title">FreeSwitchOS Port Configuration</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<h1 data-i18n="port_heading">Port Configuration</h1>
<form id="vform" action="/vlan.html">
<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>
<script src="/ports.js"></script>
</form>
</div>
<script src="/navigation.js"></script>
</body>
</html>
-190
View File
@@ -1,190 +0,0 @@
var mtus = new Int16Array(10);
var clicked = new Int8Array(10);
function createPortTable() {
var tbl = document.getElementById('speedtable');
if (tbl.rows.length <= 2 && numPorts) {
const sSelect = '<select name="speed_sel" id="speed_sel">'
+ '<option value="auto">' + t('port_auto') + '</option>'
+ '<option value="2g5">' + t('port_2500m') + '</option>'
+ '<option value="1g">' + t('port_1000m') + '</option>'
+ '<option value="100m full">' + t('port_100m_f') + '</option>'
+ '<option value="100m half">' + t('port_100m_h') + '</option>'
+ '<option value="10m full">' + t('port_10m_f') + '</option>'
+ '<option value="10m half">' + t('port_10m_h') + '</option>'
+ '</select>';
const dSwitch = '<input type="checkbox" id="disable_port" onchange="portOnOff();">'
for (let i = 1; i <= numPorts; i++) {
if (pIsSFP[i-1])
continue;
const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + i));
let portName = portNames[physToLogPort[i-1]] || '';
td = tr.insertCell(); td.appendChild(document.createTextNode(portName));
td = tr.insertCell(); td.innerHTML = linkText(pState[i-1] + 1);
tr.insertCell(); // filled by devRender()
td = tr.insertCell(); td.innerHTML = sSelect.replaceAll("speed_sel", "speed_sel_" + i);
td = tr.insertCell(); td.innerHTML = dSwitch.replaceAll("disable_port", "disable_port_" + i)
.replace("portOnOff()", "portOnOff(" + i + ")");
var button = '<button type="button" style="margin: 0 0 0 24px" onclick="applySpeed(' + i + ');">' + t('port_apply') + '</button>';
td = tr.insertCell();
td.innerHTML = button;
}
}
tbl = document.getElementById('mtutable');
if (tbl.rows.length <= 2 && numPorts) {
const mSelect = '<select name="mtu_sel" id="mtu_sel">'
+ '<option value="16383">16383</option>'
+ '<option value="1522">1522</option>'
+ '<option value="1536">1536</option>'
+ '<option value="1552">1552</option>'
+ '<option value="9216">9216</option>'
+ '</select>';
var tr = tbl.insertRow();
for (let i = 1; i <= numPorts; i++) {
let td = tr.insertCell();
if (pIsSFP[i-1])
td.innerHTML = '<object type="image/svg+xml" data="sfp.svg" width="60"></object>'
else
td.innerHTML = '<object type="image/svg+xml" data="port.svg" width="40"></object>'
}
tr = tbl.insertRow();
for (let i = 1; i <= numPorts; i++) {
let td = tr.insertCell();
td.innerHTML = mSelect.replaceAll("mtu_sel", "mtu_sel_" + i);
}
tr = tbl.insertRow();
for (let i = 1; i <= numPorts; i++) {
let td = tr.insertCell();
td.innerHTML = '<button type="button" style="margin: 0 0 0 24px" onclick="applyMTU(' + i + ');">' + t('port_apply') + '</button>';
}
}
}
function updatePortTable() {
console.log("updatePortTable called");
var tbl = document.getElementById('speedtable');
if (tbl.rows.length <= 2 || !numPorts)
return;
for (let i = 1; i <= numPorts ; i++) {
if (pIsSFP[i-1])
continue;
tbl.rows[i].cells[2].innerHTML = linkText(pState[i-1]+1);
if (!clicked[i] && pState[i - 1] < 0) {
document.getElementById('speed_sel_' + i).disabled = true;
document.getElementById('disable_port_' + i).checked = true;
}
}
}
async function applySpeed(port) {
var speed = document.getElementById('speed_sel_' + port).value;
var disabled = document.getElementById('disable_port_' + port).checked;
var cmd = "port " + port + " ";
if (!disabled)
cmd = cmd + speed;
else
cmd = cmd + "off";
console.log("CMD: " + cmd);
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
console.log('Completed!', response);
} catch(err) {
console.error(`Error: ${err}`);
}
}
async function portOnOff(p) {
var disabled = document.getElementById('disable_port_' + p).checked;
document.getElementById('speed_sel_' + p).disabled = disabled;
clicked[p] = 1;
}
async function applyMTU(port) {
var mtu = document.getElementById('mtu_sel_' + port).value;
var cmd = "mtu " + port + " " + mtu;
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
console.log('MTU Completed!', response);
getMTUs();
} catch(err) {
console.error(`Error: ${err}`);
}
}
function devRender(entries) {
var tbl = document.getElementById('speedtable');
if (tbl.rows.length <= 2 || !numPorts)
return;
var perPort = {};
for (var i = 0; i < entries.length; i++) {
var e = entries[i];
if (e.port == 'CPU')
continue;
if (!perPort[e.port])
perPort[e.port] = [];
if (perPort[e.port].indexOf(e.mac) < 0)
perPort[e.port].push(e.mac);
}
for (let i = 1; i <= numPorts; i++) {
if (pIsSFP[i-1])
continue;
var cell = tbl.rows[i].cells[3];
var macs = perPort[i] || [];
if (macs.length == 1) {
cell.textContent = macs[0];
cell.title = '';
} else if (macs.length > 1) {
cell.textContent = macs.length + ' ' + t('port_devices');
cell.title = macs.join('\n');
} else {
cell.textContent = '';
cell.title = '';
}
}
}
function devWalk() {
walkL2(function(entries, ok) {
if (ok)
devRender(entries);
setTimeout(devWalk, 15000);
});
}
function getMTUs() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
console.log("MTUS: ", JSON.stringify(s));
for (let i = 0; i < s.length; i++) {
p = s[i];
let n = p.portNum;
mtus[n] = parseInt(p.mtu, 16);
var mtu = document.getElementById('mtu_sel_' + n);
if (!mtu)
continue;
mtu.value = mtus[n];
}
}
};
xhttp.open("GET", "/mtu.json", true);
xhttp.timeout = 1500; sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
update( () => {
createPortTable();
updatePortTable();
getMTUs()
setTimeout(devWalk, 3000);
const interval = setInterval(update, 2000);
const updatePortTableInterval = setInterval(updatePortTable, 1000);
});
});
-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

-48
View File
@@ -1,48 +0,0 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="stat_title">FreeSwitchOS Port Statistics</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;
}
</style>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<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>
</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>
<script src="/stat.js"></script>
</table>
</div>
</body>
<script src="/navigation.js"></script>
</html>
-207
View File
@@ -1,207 +0,0 @@
const mib_counters = [
"Interface in Octets", 8,
"", 0,
"Interface out Octets", 8,
"", 0,
"Interface in Unicast Pkts", 8,
"", 0,
"Interface in Multicast Pkts", 8,
"", 0,
"Interface in Broadcast Pkts", 8,
"", 0,
"Interface out Unicast Pkts", 8, // 10
"", 0,
"Interface out Multicast Pkts", 8,
"", 0,
"Interface out Broadcast Pkts", 8,
"", 0,
"Interface out discards", 4,
"802.1d Tp Port in discards", 4,
"802.3 Single collision frames", 4,
"802.3 Multi collision frames", 4,
"802.3 Deferred transmissions", 4, // 20
"802.3 Late collisions", 4,
"802.3 Excessive collisions", 4,
"802.3 Symbol errors", 4,
"802.3 Control in unknown opcodes", 4,
"802.3 In Pause frames", 4,
"802.3 Out Pause frames", 4,
"Ether drop events", 4,
"TX Ether Broadcast Pkts", 4,
"TX Ether Multicast Pkts", 4,
"TX Ether CRC Align errors", 4, // 30
"RX Ether CRC Align errors", 4,
"TX Ether Undersized Pkts", 4,
"RX Ether Undersized Pkts", 4,
"TX Ether Oversized Pkts", 4,
"RX Ether Oversized Pkts", 4,
"TX Ether Fragments", 4,
"RX Ether fragments", 4,
"TX Ether Jabbers", 4,
"RX Ether Jabbers", 4,
"TX Ether Collisions", 4, // 40
"TX Ether Pkts 640 Octets", 4,
"RX Ether Pkts 640 Octets", 4,
"TX Ether 65-127 Octets", 4,
"RX Ether 65-127 Octets", 4,
"TX Ether Pkts 128-255 Octets", 4,
"RX Ether Pkts 128-255 Octets", 4,
"TX Ether Pkts 256-511 Octets", 4,
"RX Ether Pkts 256-511 Octets", 4,
"TX Ether Pkts 512-1023 Octets", 4,
"RX Ether Pkts 512-1023 Octets", 4, // 50
"TX Ether Pkts 1024-1518 Octets", 4,
"RX Ether Pkts 1024-1518 Octets", 4,
"", 4,
"RX Ether Undersized Drop Pkts", 4, // 54
"TX Ether Pkts >1518 Octets", 4,
"RX Ether Pkts >1518 Octets", 4,
"TX Ether Pkts too large", 4,
"RX Ether Pkts too large", 4,
"TX Ether Flexible Octets Set 1", 4,
"RX Ether Flexible Octets Set 1", 4,// 60
"TX Ether Flexible Octets CRC Set 1", 4,
"RX Ether Flexible Octets CRC Set 1", 4,
"TX Ether Flexible Octets Set 0", 4,
"RX Ether Flexible Octets Set 0", 4,
"TX Ether Flexible Octets CRC Set 0", 4,
"RX Ether Flexible Octets CRC Set 0", 4,
"Lenth Field Errors", 4,
"False Carriers", 4,
"Undersized Octets", 4,
"Framing Errors", 4, // 70
"", 4,
"RX MAC Discards", 4, // 72
"RX MAC IPG Short Drop", 4,
"", 4,
"802.1d TP Learned Entry Discards", 4, // 75
"Egress Queue 7 Dropped Pkts", 4,
"Egress Queue 6 Dropped Pkts", 4,
"Egress Queue 5 Dropped Pkts", 4,
"Egress Queue 4 Dropped Pkts", 4,
"Egress Queue 3 Dropped Pkts", 4, // 80
"Egress Queue 2 Dropped Pkts", 4,
"Egress Queue 1 Dropped Pkts", 4,
"Egress Queue 0 Dropped Pkts", 4,
"Egress Queue 7 Out Pkts", 4,
"Egress Queue 6 Out Pkts", 4,
"Egress Queue 5 Out Pkts", 4,
"Egress Queue 4 Out Pkts", 4,
"Egress Queue 3 Out Pkts", 4,
"Egress Queue 2 Out Pkts", 4,
"Egress Queue 1 Out Pkts", 4, // 90
"Egress Queue 0 Out Pkts", 4,
"TX Good Counter", 8,
"", 0,
"RX Good Counter", 8,
"", 0,
"RX Error Counter", 4,
"TX Error Counter", 4,
"TX Good Counter PHY", 8,
"", 0,
"RX Good Counter PHY", 8, // 100
"", 0,
"RX Error Counter PHY", 4,
"TX Error Counter PHY", 4
];
function getCounters(port) {
var xhttp = new XMLHttpRequest();
const popup = document.getElementById('popup');
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
console.log("Counters: ", JSON.stringify(s));
const ptext = document.getElementById('popup_text');
var tableHtml = "<table style='width:100%'> <tr> <th>" + t('stat_counter') + "</th> <th>" + t('stat_value') + "</th> <th>" + t('stat_counter') + "</th> <th>" + t('stat_value') + "</th></tr> <tr>";
console.log("Counter 0: ", BigInt(s[0]).toString(), " length: ", s.length);
var c = 0;
for (i = 0; i < mib_counters.length; i += 4) {
console.log(i, " ", mib_counters[i], ": ", mib_counters[i + 1]);
if (mib_counters[i] == "" && mib_counters[i + 1] == 8) {
console.log("c " + i + ": continue");
continue;
}
var count = BigInt(s[i / 4]);
if (mib_counters[i + 1] == 8) {
tableHtml += "<td>" + mib_counters[i] + "</td><td>" + count.toString() + "</td>";
c += 1;
} else if (mib_counters[i + 1] == 4) {
if (mib_counters[i] != "") {
tableHtml += "<td>" + mib_counters[i] + "</td><td>" + (count >> 32n).toString() + "</td>";
c += 1;
}
if (c == 2) {
tableHtml += "</tr> <tr>";
c = 0;
}
if (mib_counters[i + 2] != "") {
tableHtml += "<td>" + mib_counters[i + 2] + "</td><td>" + (count & 4294967295n).toString() + "</td>";
c += 1;
}
}
if (c == 2) {
tableHtml += "</tr> <tr>";
c = 0;
}
}
ptext.innerHTML = tableHtml + "</tr></table>";
popup.style.display = 'flex';
}
};
xhttp.open("GET", "/counters.json?port=" + port, true);
xhttp.timeout = 1500; sendXHTTP(xhttp);
}
function fillStats() {
var tbl = document.getElementById('statstable');
if (!numPorts)
return;
if (tbl.rows.length > 1) {
for (let i = 0; i < numPorts; i++) {
console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] + 1]);
tbl.rows[i + 1].cells[2].innerHTML = linkText(pState[i] + 1);
tbl.rows[i + 1].cells[3].innerHTML = `${txG[i]}` + t('common_pkts');
tbl.rows[i + 1].cells[4].innerHTML = `${txB[i]}` + t('common_pkts');
tbl.rows[i + 1].cells[5].innerHTML = `${rxG[i]}` + t('common_pkts');
tbl.rows[i + 1].cells[6].innerHTML = `${rxB[i]}` + t('common_pkts');
}
} else {
for (let i = 0; i < numPorts; i++) {
console.log("Table row: " + i);
const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i + 1)));
let portName = portNames[physToLogPort[i]] || '';
td = tr.insertCell(); td.appendChild(document.createTextNode(portName));
td = tr.insertCell(); td.appendChild(document.createTextNode(linkText(pState[i] + 1)));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]}` + t('common_pkts')));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txB[i]}` + t('common_pkts')));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${rxG[i]}` + t('common_pkts')));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${rxB[i]}` + t('common_pkts')));
var button = '<button type="button" style="margin: 0 0 0 24px" onclick="getCounters(' + (i + 1) + ');">' + t('stat_show') + '</button>';
td = tr.insertCell(); td.innerHTML = button;
}
}
}
const popup = document.getElementById('popup');
const closePopup = document.getElementById('closePopup');
closePopup.addEventListener('click', () => {
popup.style.display = 'none';
});
window.addEventListener('click', (event) => {
if (event.target === popup) {
popup.style.display = 'none';
}
});
window.addEventListener("load", function () {
update(() => {
update();
fillStats();
const stat = setInterval(fillStats, 1000);
const interval = setInterval(update, 2000);
});
});
-47
View File
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title>FreeSwitchOS Spanning Tree</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<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>
<script src="/stp.js"></script>
</div>
</body>
<script src="/navigation.js"></script>
</html>
-187
View File
@@ -1,187 +0,0 @@
const STP_STATES = ["Disabled", "Blocking", "Learning", "Forwarding"];
const STP_ROLES = ["-", "Root", "Designated", "Alternate"];
const PF_ENABLED = 1, PF_ADMEDGE = 2, PF_AUTOEDGE = 4, PF_BPDUGUARD = 8,
PF_ROOTGUARD = 16, PF_FILTER = 32, PF_OPEREDGE = 64, PF_TRIPPED = 128;
var stpDirty = false;
var stpRows = 0; // ports table built?
async function stpCmd(cmd) {
stpDirty = true;
try {
await fetch('/cmd', { method: 'POST', body: cmd });
} catch(err) {
console.error(`Error: ${err}`);
}
stpDirty = false;
fetchStp();
}
function sel(id, opts, onch) {
const s = document.createElement("select");
s.id = id;
for (const [v, label] of opts) {
const o = document.createElement("option");
o.value = v; o.textContent = label;
s.appendChild(o);
}
s.addEventListener("change", onch);
return s;
}
function num(id, min, max, onch) {
const n = document.createElement("input");
n.type = "number"; n.id = id; n.min = min; n.max = max; n.style.width = "4em";
n.addEventListener("change", onch);
return n;
}
function buildPortsTable(ports) {
const tbl = document.getElementById("stpPortsTbl");
const stat = document.getElementById("stpStatTbl");
for (const p of [...ports].sort((a, b) => a.p - b.p)) {
const tr = tbl.insertRow();
tr.insertCell().textContent = p.p; // Port
tr.insertCell().appendChild(sel("en_" + p.p,
[["on","Enable"],["off","Disable"]],
e => stpCmd("stp port " + p.p + " " + e.target.value)));
const pc = num("cost_" + p.p, 0, 200000000,
e => stpCmd("stp port " + p.p + " cost " + e.target.value));
pc.style.width = "7em";
pc.title = "0 - 200000000 (0 = Auto)";
tr.insertCell().appendChild(pc);
const pr = sel("prio_" + p.p, [],
e => stpCmd("stp port " + p.p + " prio " + e.target.value));
for (let v = 0; v <= 240; v += 16) {
const o = document.createElement("option");
o.value = v; o.textContent = v + (v === 128 ? " (default)" : "");
pr.appendChild(o);
}
tr.insertCell().appendChild(pr);
tr.insertCell().appendChild(sel("edge_" + p.p,
[["auto","Auto"],["on","Enable"],["off","Disable"]],
e => stpCmd("stp port " + p.p + " edge " + e.target.value)));
tr.insertCell().appendChild(sel("filt_" + p.p,
[["off","Disable"],["on","Enable"]],
e => stpCmd("stp port " + p.p + " filter " + e.target.value)));
tr.insertCell().appendChild(sel("guard_" + p.p,
[["none","None"],["bpdu","BPDU"],["root","Root"]],
e => stpCmd("stp port " + p.p + " guard " + e.target.value)));
tr.insertCell().appendChild(sel("p2p_" + p.p,
[["auto","Auto"],["on","Enable"],["off","Disable"]],
e => stpCmd("stp port " + p.p + " p2p " + e.target.value)));
const sr = stat.insertRow();
sr.insertCell().textContent = p.p;
for (const id of ["st","role","db","dp","dc","oe","op"])
sr.insertCell().id = id + "_" + p.p;
}
stpRows = ports.length;
}
function bridgeSelf(s) {
return fmtBridgeId((s.prio * 4096).toString(16).padStart(4, "0") + s.myMac);
}
function fmtBridgeId(h) {
if (!h || h.length < 16) return "";
const prio = parseInt(h.slice(0, 4), 16);
const mac = h.slice(4).replace(/(..)(?=.)/g, "$1:");
return prio + "-" + mac.toUpperCase();
}
function fetchStp() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
if (!stpRows)
buildPortsTable(s.ports);
document.getElementById("stpStat").textContent = s.on
? (s.weRoot
? "This switch (" + bridgeSelf(s) + ") is the root bridge — topology changes: "
+ parseInt(s.tc, 16)
: "This switch: " + bridgeSelf(s)
+ " — root bridge: " + fmtBridgeId(s.rootPrio + s.rootMac)
+ " via port " + s.rootPort + " — path cost: " + parseInt(s.cost, 16)
+ " — topology changes: " + parseInt(s.tc, 16))
: "";
for (const p of s.ports) {
const trip = (p.f & PF_TRIPPED) ? " (guard!)" : "";
document.getElementById("st_" + p.p).textContent =
s.on ? STP_STATES[p.st] + trip : "-";
document.getElementById("role_" + p.p).textContent =
s.on ? STP_ROLES[p.role] : "-";
document.getElementById("db_" + p.p).textContent = s.on ? fmtBridgeId(p.db) : "-";
document.getElementById("dp_" + p.p).textContent =
s.on ? (parseInt(p.dp.slice(0, 2), 16) + "-" + parseInt(p.dp.slice(2), 16)) : "-";
document.getElementById("dc_" + p.p).textContent = s.on ? parseInt(p.dc, 16) : "-";
document.getElementById("oe_" + p.p).textContent =
s.on ? ((p.f & PF_OPEREDGE) ? "True" : "False") : "-";
document.getElementById("op_" + p.p).textContent = s.on ? (p.p2 == 2 ? "False" : "True") : "-";
}
if (stpDirty) // an edit is in flight - do not revert controls
return;
document.getElementById("stpMode").value = s.on ? "on" : "off";
document.getElementById("bPrio").value = s.prio;
document.getElementById("bVer").value = s.rstp ? "rstp" : "stp";
document.getElementById("bHello").value = s.hello;
document.getElementById("bMaxage").value = s.maxage;
document.getElementById("bFwd").value = s.fwd;
document.getElementById("bTxhold").value = s.txhold;
for (const p of s.ports) {
document.getElementById("en_" + p.p).value = (p.f & PF_ENABLED) ? "on" : "off";
document.getElementById("edge_" + p.p).value =
(p.f & PF_ADMEDGE) ? "on" : ((p.f & PF_AUTOEDGE) ? "auto" : "off");
document.getElementById("cost_" + p.p).value = parseInt(p.pc, 16);
document.getElementById("prio_" + p.p).value = p.prio;
document.getElementById("p2p_" + p.p).value = ["auto","on","off"][p.p2];
document.getElementById("guard_" + p.p).value =
(p.f & PF_BPDUGUARD) ? "bpdu" : ((p.f & PF_ROOTGUARD) ? "root" : "none");
document.getElementById("filt_" + p.p).value = (p.f & PF_FILTER) ? "on" : "off";
}
}
};
xhttp.open("GET", `/stp.json`, true);
sendXHTTP(xhttp);
}
async function stpSub() {
const on = document.getElementById("stpMode").value === "on";
document.getElementById("stpStat").textContent = on
? "Enabling STP. The ports start blocked and take up to "
+ (2 * document.getElementById("bFwd").value)
+ " s to reach forwarding, and this page can stay silent until they do."
: "Disabling STP.";
await stpCmd(on ? "stp on" : "stp off");
}
window.addEventListener("load", function() {
const bp = document.getElementById("bPrio");
for (let i = 0; i < 16; i++) {
const o = document.createElement("option");
o.value = i; o.textContent = (i * 4096) + (i === 8 ? " (default)" : "");
bp.appendChild(o);
}
bp.addEventListener("change", e => stpCmd("stp prio " + e.target.value));
document.getElementById("bVer")
.addEventListener("change", e => stpCmd("stp version " + e.target.value));
document.getElementById("bHello")
.addEventListener("change", e => stpCmd("stp hello " + e.target.value));
document.getElementById("bMaxage")
.addEventListener("change", e => stpCmd("stp maxage " + e.target.value));
document.getElementById("bFwd")
.addEventListener("change", e => stpCmd("stp fwd " + e.target.value));
document.getElementById("bTxhold")
.addEventListener("change", e => stpCmd("stp txhold " + e.target.value));
document.getElementById("stpMode")
.addEventListener("change", () => { stpDirty = true; });
update( () => {
fetchStp();
const interval = setInterval(update, 2000);
const stpInt = setInterval(fetchStp, 2000);
});
});
-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

-109
View File
@@ -1,109 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="sys_title">System Settings</title>
<style>
.tab-bar { display: flex; border-bottom: 2px solid #226; margin-bottom: 0; margin-left: 16%; 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>
<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>
</div>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></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"/>
<button onclick="hostSub()" data-i18n="sys_apply">Apply</button></div>
</div>
<div class="row">
<div class="lcol"> <label data-i18n="sys_model">Model:</label></div>
<div class="rcol"><span id="model" class="rotext"></span></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" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
</div>
<div class="row">
<div class="lcol"> <label for="netmask" data-i18n="sys_netmask">Netmask:</label></div>
<div class="rcol"><input id="netmask" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
</div>
<div class="row">
<div class="lcol"> <label for="gw" data-i18n="sys_gateway">Gateway:</label></div>
<div class="rcol"><input id="gw" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
</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" class="ip" onchange="mgmtVlanChanged()"></select></div>
</div>
<div class="row">
<div class="lcol"> <label data-i18n="sys_language">Language:</label></div>
<div class="rcol">
<select id="lang-select" onchange="changeLang()">
<option value="en">English</option>
<option value="ja">日本語</option>
<option value="zh">中文</option>
</select>
</div>
</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>
<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>
</div>
</div>
<script src="/config.js"></script>
<script src="/system.js"></script>
<script src="/navigation.js"></script>
<script>
function openTab(evt, tabId) {
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById(tabId).classList.add('active');
evt.currentTarget.classList.add('active');
}
</script>
</body>
</html>
-222
View File
@@ -1,222 +0,0 @@
var systemInterval = Number();
var isSaving = false;
const ips = ["ip", "netmask", "gw"];
function changeLang() {
var lang = document.getElementById('lang-select').value;
setLang(lang);
}
function checkIp(ip) {
const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipv4.test(ip)) {alert(t('sys_invalid_ip') + ip); return false };
return true;
}
async function ipSub() {
for (let i=0;i<3;i++) {
if (!checkIp(document.getElementById(ips[i]).value))
return;
}
var cmd = '';
for (let i=0; i<3;i++){
cmd += ips[i]+' '+document.getElementById(ips[i]).value+'\n';
}
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
console.log('Completed!', response);
fetchIP();
} catch(err) {
console.error(`Error: ${err}`);
}
}
async function cmdSub() {
const input = document.getElementById('console_cmd');
const out = document.getElementById('console_out');
const cmd = input.value;
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
if (response.status == 401) {
window.location.href = 'login.html';
return;
}
let text = await response.text();
if (text != "" && !text.endsWith("\n"))
text += "\n";
if (out.textContent.length > 20000)
out.textContent = out.textContent.slice(-16000);
out.textContent += "> " + cmd + "\n" + text;
out.scrollTop = out.scrollHeight;
input.value = "";
} catch(err) {
out.textContent += "> " + cmd + "\n" + err + "\n";
console.error(`Error: ${err}`);
}
}
async function hostSub() {
const h = document.getElementById("hostname").value;
try { await fetch('/cmd', { method: 'POST', body: "hostname " + h }); }
catch(err) { console.error(`Error: ${err}`); }
fetchIP();
}
async function sendConfig(c) {
if (isSaving) return;
isSaving = true;
clearInterval(systemInterval);
const form = new FormData();
form.append("MAX_FILE_SIZE", "4096");
form.append("configuration", new Blob([c], {type: "application/octet-stream"}), "config.txt");
try {
const response = await fetch('/config', {
method: 'POST',
body: form
});
console.log('Completed!', response);
try {
await fetch('/cmd_log_clear', { method: 'GET' });
} catch(e) {}
} catch(err) {
console.error(`Error: ${err}`);
} finally {
isSaving = false;
systemInterval = setInterval(fetchIP, 1000);
}
}
async function flashSave() {
configuration = [];
const savedConfig = await fetchConfig();
const cmdLog = await fetchCmdLog();
if (savedConfig) parseConf(savedConfig);
if (cmdLog) parseConf(cmdLog);
const body = configuration.join('\n') + '\n';
console.log("CONFIGURATION to save: ", body);
await sendConfig(body);
}
async function flashStartupSave() {
var configContent = document.getElementById("config_display").value;
console.log("CONFIGURATION to save: ", configContent);
sendConfig(configContent);
// Clear the command log 1 second after initiating the config save
setTimeout(() => {
fetch('/cmd_log_clear', { method: 'GET' })
.then(response => console.log('Command log cleared', response))
.catch(err => console.error('Error clearing command log:', err));
}, 1000);
}
function clearConfig() {
document.getElementById("config_display").value = "";
// Validate and populate with current IP settings
for (let i=0; i<3; i++) {
if (!checkIp(document.getElementById(ips[i]).value))
return;
}
var configLines = "";
for (let i=0; i<3; i++){
var cmd = ips[i]+' '+document.getElementById(ips[i]).value;
configLines += cmd + "\n";
}
document.getElementById("config_display").value = configLines;
}
function fetchIP() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
console.log("IP: ", s);
document.getElementById("ip").value=s.ip_address;
document.getElementById("netmask").value=s.ip_netmask;
document.getElementById("gw").value=s.ip_gateway;
document.getElementById("hostname").value=s.hostname;
document.getElementById("model").textContent=s.hw_ver;
loadMgmtVlan();
clearInterval(systemInterval);
// Fetch and populate the config textbox
fetchConfig().then((configText) => {
let fullConfig = configText;
// Fetch and append cmd_log
//return fetchCmdLog().then((cmdLogText) => {
// if (cmdLogText) {
// fullConfig = fullConfig + cmdLogText;
// }
document.getElementById("config_display").value = fullConfig;
});
};
}
xhttp.open("GET", `/information.json`, true);
xhttp.send();
}
function resetSwitch() {
if (!confirm(t('sys_reset_confirm'))) {
return;
}
fetch('/reset', { method: 'GET' }).catch(() => {});
setTimeout(() => {
alert(t('sys_resetting'));
}, 3000);
}
window.addEventListener("load", function() {
var langSel = document.getElementById('lang-select');
if (langSel) langSel.value = rtlLang;
systemInterval = setInterval(fetchIP, 1000);
});
var mgmtVlanCurrent = 0;
function loadMgmtVlan() {
var sel = document.getElementById('mgmtvlan');
if (!sel) return;
fetch('/vlanlist').then(function(r) { return r.json(); }).then(function(d) {
var cur = d.mgmt || 0;
var list = d.vlan || [];
mgmtVlanCurrent = cur;
sel.innerHTML = '';
if (!cur) {
var none = document.createElement('option');
none.value = 0; none.disabled = true;
none.textContent = t('sys_mgmt_untagged');
sel.appendChild(none);
}
for (var i = 0; i < list.length; i++) {
var o = document.createElement('option');
o.value = list[i].id;
o.textContent = list[i].name ? (list[i].id + ' (' + list[i].name + ')') : list[i].id;
sel.appendChild(o);
}
sel.value = cur;
}).catch(function(err) { console.error('VLAN list failed:', err); });
}
function mgmtVlanChanged() {
var sel = document.getElementById('mgmtvlan');
var id = parseInt(sel.value, 10);
if (!id || id === mgmtVlanCurrent) return;
if (!confirm(t('sys_mgmt_confirm') + id + '.\n\n' + t('sys_mgmt_warn'))) {
sel.value = mgmtVlanCurrent;
return;
}
fetch('/cmd', { method: 'POST', body: 'vlan ' + id + ' mgmt' })
.then(function() { mgmtVlanCurrent = id; })
.catch(function(err) { console.error('Set management VLAN failed:', err); sel.value = mgmtVlanCurrent; });
}
-20
View File
@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="update_title">Firmware update</title>
</head>
<body>
<nav id="sidebar"></nav>
<div 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>
<script src="/navigation.js"></script>
</body>
</html>
-59
View File
@@ -1,59 +0,0 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="vlan_title">FreeSwitchOS VLAN Configuration</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<h1 data-i18n="vlan_heading">VLAN Configuration</h1>
<form id="vform" action="/vlan.html">
<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>
<script src="/vlan.js"></script>
<br/> <input style="width:40%;" class="action" id="vlan_sub" onclick="vlanSub();" type="button" data-i18n="vlan_update" value="Update / Create">
<script src="/vlan_sub.js"></script>
</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>
<script src="/navigation.js"></script>
</body>
</html>
-211
View File
@@ -1,211 +0,0 @@
var vlanInterval = Number();
function vlanForm() {
if (!numPorts)
return;
var t = document.getElementById('tPorts');
var u = document.getElementById('uPorts');
var p = document.getElementById('pPorts');
for (let i = 1; i <= numPorts; i++) {
const d = document.createElement("div");
d.classList.add("cbgroup");
const l = document.createElement("label");
l.innerHTML = "" + i;
l.classList.add("cbgroup");
const inp = document.createElement("input");
inp.type = "checkbox"; inp.setAttribute("class","psel");
inp.id = "tport" + i;
inp.setAttribute('onclick', `setC("u", ${i}, false);`);
const o = document.createElement("img");
if (pIsSFP[i - 1]) {
o.src = "sfp.svg"; o.width ="60"; o.height ="60";
} else {
o.src = "port.svg"; o.width = "40"; o.height = "40";
}
l.appendChild(inp); l.appendChild(o);
d.appendChild(l)
t.appendChild(d);
var d2=d.cloneNode(true);
d2.children[0].children[0].id = "uport" + i;
d2.children[0].children[0].setAttribute('onclick', `setC("t", ${i}, false);`);
u.appendChild(d2);
var d3=d.cloneNode(true);
d3.children[0].children[0].id = "pport" + i;
d3.children[0].children[0].removeAttribute('onclick');
p.appendChild(d3);
}
}
function setC(t, p, c){
document.getElementById(t+'port'+p).checked=c;
}
function utClicked(t){
for (let i = 1; i <= numPorts; i++) {
setC('t', i, t); setC('u', i, !t);
}
}
function pvClicked(p){
for (let i = 1; i <= numPorts; i++) {
setC('p', i, p);
}
}
function fetchVLAN() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
console.log("VLAN: ", JSON.stringify(s));
m = parseInt(s.members, 16);
document.getElementById('vname').value = s.name;
var members = m & 0x3FF;
var untag = (m >> 10) & 0x3FF;
var pvid = parseInt(s.pvid, 16);
console.log("PVID: ", pvid);
for (let p = 1; p <= numPorts; p++) {
var bit = physToLogPort[p-1];
var isMember = (members >> bit) & 1;
var isUntag = (untag >> bit) & 1;
setC('t', p, isMember && !isUntag);
setC('u', p, isMember && isUntag);
setC('p', p, (pvid >> bit) & 1);
}
}
};
var v=document.getElementById('vid').value
if (!v) {
alert(t('vlan_set_id_first'));
return;
}
xhttp.open("GET", `/vlan.json?vid=${v}`, true);
sendXHTTP(xhttp);
}
function portsToRange(mask, nPorts) {
var parts = [];
var start = -1, prev = -1;
for (var p = 1; p <= nPorts; p++) {
var bit = physToLogPort[p - 1];
if ((mask >> bit) & 1) {
if (start < 0) start = p;
prev = p;
} else {
if (start >= 0) {
parts.push(start === prev ? String(start) : start + '-' + prev);
start = -1; prev = -1;
}
}
}
if (start >= 0)
parts.push(start === prev ? String(start) : start + '-' + prev);
return parts.length ? parts.join(',') : '-';
}
async function loadVlanTable() {
var tbody = document.getElementById('vlanTableBody');
if (!tbody) return;
tbody.innerHTML = '';
var resp;
try { resp = await fetch('/vlanlist'); } catch(e) { return; }
if (!resp.ok) return;
var vlans = (await resp.json()).vlan || [];
for (var i = 0; i < vlans.length; i++) {
var v = vlans[i];
var vresp;
try { vresp = await fetch('/vlan.json?vid=' + v.id); } catch(e) { continue; }
if (!vresp.ok) continue;
var s = await vresp.json();
var m = parseInt(s.members, 16);
var members = m & 0x3FF;
var untag = ((m >> 10) & 0x3FF) & members;
var tagged = members & ~untag;
var pvid = parseInt(s.pvid, 16) & 0x3FF;
var tr = document.createElement('tr');
var td, a, btn;
td = document.createElement('td');
a = document.createElement('a');
a.href = '#';
a.textContent = v.id;
(function(vid) {
a.onclick = function(e) {
e.preventDefault();
document.getElementById('vid').value = vid;
fetchVLAN();
};
})(v.id);
td.appendChild(a); tr.appendChild(td);
td = document.createElement('td');
td.textContent = v.name || ''; tr.appendChild(td);
td = document.createElement('td');
td.textContent = portsToRange(members, numPorts); tr.appendChild(td);
td = document.createElement('td');
td.textContent = portsToRange(tagged, numPorts); tr.appendChild(td);
td = document.createElement('td');
td.textContent = portsToRange(untag, numPorts); tr.appendChild(td);
td = document.createElement('td');
td.textContent = portsToRange(pvid, numPorts); tr.appendChild(td);
td = document.createElement('td');
if (v.id !== 1) {
btn = document.createElement('button');
btn.textContent = '✕';
(function(vid) {
btn.onclick = function() { deleteVlan(vid); };
})(v.id);
td.appendChild(btn);
}
tr.appendChild(td);
tbody.appendChild(tr);
}
}
function deleteVlan(id) {
if (!confirm(t('vlan_delete_confirm') + id + '?')) return;
fetch('/cmd', { method: 'POST', body: 'vlan ' + id + ' d' })
.then(function() { refreshVlanViews(); })
.catch(function(err) { console.error('Delete failed:', err); });
}
function refreshVlanViews() {
loadVlanList();
loadVlanTable();
}
function loadVlanList() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState !== 4) return;
var sel = document.getElementById('vlanSelect');
if (this.status !== 200) {
sel.style.display = 'none';
return;
}
var vlans = JSON.parse(this.responseText).vlan || [];
if (!vlans.length) {
sel.style.display = 'none';
return;
}
sel.options.length = 1;
for (var i = 0; i < vlans.length; i++) {
var opt = document.createElement('option');
opt.value = vlans[i].id;
opt.text = vlans[i].name ? vlans[i].id + ' — ' + vlans[i].name : String(vlans[i].id);
sel.appendChild(opt);
}
};
xhttp.open('GET', '/vlanlist', true);
sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
update( () => {
vlanForm();
refreshVlanViews();
document.getElementById('vlanSelect').onchange = function() {
document.getElementById('vid').value = this.value;
fetchVLAN();
};
const interval = setInterval(update, 2000);
});
});
-36
View File
@@ -1,36 +0,0 @@
async function vlanSub() {
var commands = [];
var cmd = "vlan ";
var v=document.getElementById('vid').value
if (!v) {
alert(t('vlan_set_id_first'));
return;
}
cmd = cmd + v;
if (document.getElementById('vname').value)
cmd = cmd + ' ' + document.getElementById('vname').value;
for (let i = 1; i <= numPorts; i++) {
if (document.getElementById('tport' + i).checked)
cmd = cmd + ` ${i}t`;
else if (document.getElementById('uport' + i).checked)
cmd = cmd + ` ${i}`;
}
commands.push(cmd);
for (let i = 1; i <= numPorts; i++) {
if (document.getElementById('pport' + i).checked)
commands.push(`pvid ${i} ${v}`);
}
try {
for (let c of commands) {
const response = await fetch('/cmd', {
method: 'POST',
body: c
});
console.log('Completed!', response);
}
refreshVlanViews();
} catch(err) {
console.error(`Error: ${err}`);
}
}
+103 -67
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
@@ -89,7 +93,7 @@ __xdata uint32_t last_session_use;
extern __xdata uint16_t crc_value;
__xdata uint16_t crc_final;
void crc16(__xdata uint8_t *v) __naked;
void crc16_bank1(__xdata uint8_t *v) __naked;
inline uint8_t is_separator(uint8_t c)
@@ -101,10 +105,11 @@ inline uint8_t is_separator(uint8_t c)
void httpd_init(void) __banked
{
config_upload = 0; // xdata is not zeroed by the startup code
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
// Start listening to port 80
uip_listen(HTONS(80));
s->tstate = TSTATE_CLOSED;
// Not through uip_conn: it only points at a connection while uIP is
// handling one, and nothing has set it yet at init time.
uip_conns[0].appstate.tstate = TSTATE_CLOSED;
fw_reset_pending = 0; // xdata is not zeroed by the startup code
}
@@ -126,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;
@@ -148,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;
@@ -340,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;
@@ -374,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);
@@ -417,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();
@@ -517,7 +531,7 @@ uint8_t stream_upload(void)
if (upload_settings.p[upload_settings.bptr] == boundary[bindex]) {
if (!bindex)
crc_final = crc_value;
crc16(upload_settings.p + upload_settings.bptr);
crc16_bank1(upload_settings.p + upload_settings.bptr);
upload_settings.bptr++;
bindex++;
} else {
@@ -526,9 +540,22 @@ uint8_t stream_upload(void)
write_len += bindex;
bindex = 0;
}
crc16(upload_settings.p + upload_settings.bptr);
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) {
@@ -558,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;
@@ -588,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;
@@ -683,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;
@@ -749,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");
@@ -776,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();
@@ -802,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;
@@ -825,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);
@@ -860,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);
@@ -886,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;
@@ -897,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;
@@ -977,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);
@@ -985,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")) {
@@ -1009,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_i18n_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
@@ -1030,7 +1067,10 @@ void httpd_appcall(void)
* response; without advertising it a browser reuses the socket
* from its keep-alive pool and the next request hits the already
* closed connection (a POST is then dropped without a retry). */
slen += strtox(outbuf + slen, "; charset=UTF-8\r\nCache-Control: max-age=60, must-revalidate\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; form-action 'self'\r\n\r\n");
slen += strtox(outbuf + slen, "; charset=UTF-8\r\n");
if (f_data[entry].gzip)
slen += strtox(outbuf + slen, "Content-Encoding: gzip\r\n");
slen += strtox(outbuf + slen, "Cache-Control: max-age=60, must-revalidate\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; form-action 'self'\r\n\r\n");
len_left = f_data[entry].len;
if (len_left > (TCP_OUTBUF_SIZE - slen)) {
@@ -1049,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;
+196 -37
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;
@@ -299,11 +356,9 @@ void send_vlan(uint16_t vlan)
bool send_counters(uint8_t phys_port)
{
uint8_t phys_port_idx = phys_port - 1;
if (phys_port_idx > 8)
if (phys_port_idx > (machine.max_port - machine.min_port))
goto err;
uint8_t log_port = machine.phys_to_log_port[phys_port_idx];
if (log_port == 0)
goto err;
dbg_string("send_counters called: "); dbg_byte(phys_port_idx); dbg_char('\n');
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
@@ -313,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(',');
@@ -434,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);
@@ -570,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;
@@ -609,17 +660,43 @@ void send_stp(void)
slen += strtox(outbuf + slen, "\",\"weRoot\":");
bool_to_html(stp_root_port == 0xff ? 1 : 0);
slen += strtox(outbuf + slen, ",\"rootPort\":");
itoa_html(stp_root_port == 0xff ? 0 : machine.log_to_phys_port[stp_root_port]);
j = stp_root_port;
if (j != 0xff && j >= STP_LAG_BASE) {
j = 0;
while (j < STP_LAG_BASE && !((stp_lag_mask[stp_root_port - STP_LAG_BASE] >> j) & 1))
j++;
if (j >= STP_LAG_BASE)
j = 0;
}
itoa_html(j == 0xff ? 0 : machine.log_to_phys_port[j]);
slen += strtox(outbuf + slen, ",\"tc\":\"");
byte_to_html(stp_tc_count >> 8);
byte_to_html(stp_tc_count);
slen += strtox(outbuf + slen, "\",\"ports\":[");
reg_read_m(RTL837X_MSTP_STATES);
for (i = machine.min_port; i <= machine.max_port; i++) {
for (i = 0; i < STP_ENTITIES; i++) {
if (i < STP_LAG_BASE && (i < machine.min_port || i > machine.max_port))
continue;
if (i < STP_LAG_BASE && stp_ent_of[i] != i)
continue;
if (i >= STP_LAG_BASE && !stp_lag_mask[i - STP_LAG_BASE])
continue;
slen += strtox(outbuf + slen, "{\"p\":");
itoa_html(machine.log_to_phys_port[i]);
itoa_html(i < STP_LAG_BASE ? machine.log_to_phys_port[i] : 0);
slen += strtox(outbuf + slen, ",\"lag\":");
itoa_html(i < STP_LAG_BASE ? 0 : i - STP_LAG_BASE + 1);
slen += strtox(outbuf + slen, ",\"mbr\":");
itoa16_html(i < STP_LAG_BASE ? 0 : stp_lag_mask[i - STP_LAG_BASE]);
slen += strtox(outbuf + slen, ",\"st\":");
st = (sfr_data[3 - (i >> 2)] >> ((i << 1) & 0x7)) & 0x3;
j = i;
if (i >= STP_LAG_BASE) {
j = 0;
while (j < STP_LAG_BASE && !((stp_lag_mask[i - STP_LAG_BASE] >> j) & 1))
j++;
if (j >= STP_LAG_BASE)
j = 0;
}
st = (sfr_data[3 - (j >> 2)] >> ((j << 1) & 0x7)) & 0x3;
itoa_html(st);
slen += strtox(outbuf + slen, ",\"role\":");
if (!(stp_pflags[i] & STP_PF_ENABLED) || (stp_pflags[i] & STP_PF_TRIPPED))
@@ -659,6 +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)
{
@@ -752,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");
@@ -788,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, "\"");
@@ -827,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");
@@ -888,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);
@@ -897,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);
@@ -964,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])
@@ -989,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;
@@ -1008,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++);
+362 -42
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},
@@ -247,6 +275,7 @@ __code const struct machine machine = {
.led_mux = {0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x09, 0x0d, 0x10,
0x11, 0x0e, 0x14, 0x11, 0x12, 0x15, 0x15, 0x16, 0x18, 0x19,
0x1a, 0x19, 0x1d, 0x1e, 0x1c, 0x1d, 0x20, 0x21},
.mac_flash_offset = 0x1FC000,
};
#elif defined MACHINE_SWGT024_V2_0_MANAGED
@@ -264,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},
@@ -305,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},
@@ -345,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},
@@ -384,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},
@@ -424,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},
@@ -457,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},
@@ -498,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},
@@ -530,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},
@@ -555,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 = {
@@ -595,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 },
@@ -640,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},
@@ -675,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,
@@ -719,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},
@@ -768,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 = {
@@ -822,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,
@@ -878,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 },
@@ -910,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",
@@ -924,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 },
@@ -967,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 },
@@ -1048,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 },
@@ -1113,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},
@@ -1144,6 +1231,239 @@ __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",
.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: RJ45 2.5G = amber, 1G/100M/10M = green */
LEDS_2G5 | LEDS_LINK | LEDS_ACT, /* amber */
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, /* green */
0,
0
},
{
/* SET1: SFP+ port = green */
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,
0,
0,
0
}
},
.led_mux_custom = 1,
.led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08,
0x09, 0x0c, 0x09, 0x0d, 0x10,
0x11, 0x0e, 0x14, 0x11, 0x12,
0x15, 0x15, 0x16, 0x18, 0x19,
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"
#endif
+11 -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,17 +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
typedef struct {
// GPIO pins for SDA/SCL
uint8_t sda;
uint8_t scl;
} i2c_bus_t;
// #define MACHINE_LIANGUO_HYWS_SGT0108S
// #define MACHINE_POE_2G080110GS
// #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
@@ -70,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
@@ -101,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;
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff. Show more