Author SHA1 Message Date
René van Dorst 787d593996 Merge pull request #337 from DrDoof/fix/igmp-dup-loop
igmp: drop the duplicate port configuration loop
2026-08-14 22:12:33 +00:00
René van Dorst db2a541e5f Merge pull request #331 from DrDoof/fix/reg-macros-braces
regs: wrap REG_SET and REG_WRITE, and fix what that uncovers
2026-08-14 21:29:03 +00:00
d00f 5c5dcb5209 igmp: drop the duplicate port configuration loop
igmp_setup() writes the per-port IGMP configuration twice, once with the
value spelled out and once with the same number assembled from the
constants: IGMP_MAX_GROUP | IGMP_PROTOCOL_ENABLE | IGMP_FLOOD is exactly
0x00ff7c15. The second loop carries the comment block explaining the bit
layout, so the literal one is the one to drop.

Today the cost is one redundant register write, because REG_SET is not a
single statement and the unbraced loop body only ever reaches index
machine.max_port + 1. Once the macro is wrapped it becomes one redundant
write per port on every boot and on every "igmp off", which is what makes
this worth removing rather than leaving.

Fifty nine bytes of BANK1 on SWTGW218AS, nothing anywhere else.
2026-08-14 07:39:24 +02:00
d00f be6d47a3ab regs: wrap REG_SET and REG_WRITE in do { } while (0)
Both macros expand to a run of statements joined by backslashes with
nothing around them, so as the unbraced body of an if or a for only the
first assignment belongs to that body. The other three and the
reg_write() call sit after it and run once, unconditionally, with
whatever the loop counter ended on. Wrapping each macro into a single
statement is what every call site already assumes it to be.

This hands the compiler no new room around the SFR writes, which is worth
showing rather than asserting. Building the whole image before and after
and comparing the generated assembly module by module, with label
numbering, block scope suffixes and the version string normalised away,
three modules differ: rtl837x_igmp, rtl837x_port and rtl837x_leds. Ten
modules call these macros, so the other seven come out identical, and so
does everything else in the image.

Those three differ because they hold the five unbraced uses. Two of them
want a fix rather than only the brace, and that is left to the commits
that follow.

Six bytes of BANK1 on SWTGW218AS, nothing anywhere else.
2026-08-14 07:34:42 +02:00
d00fandd00f 4ff009dbfc uip: cap TCP MSS to 1460 to survive jumbo-MTU clients (#298)
* uip: parenthesise UIP_LLH_LEN

The macro expands to a bare sum, so wherever it is subtracted the second term
gets added instead. UIP_TCP_MSS - and with it UIP_RECEIVE_WINDOW - therefore
comes out 24 bytes above the buffer's real capacity. UIP_APPDATA_SIZE and
UIP_REASS_BUFSIZE are wrong the same way, though neither is reachable today.

The additions, uip_buf[UIP_LLH_LEN] and friends, were right by luck.

* uip: keep the advertised MSS below the buffer edge

Deriving the MSS straight from the buffer size makes the switch advertise
exactly the segment that fills uip_buf to its last byte, and a peer that takes
it literally corrupts every large upload: the firmware image arrives fully
acknowledged, with no retransmissions on the wire, yet the CRC over the
streamed body never matches and the flash write is abandoned.

Isolated by changing nothing but the segment size, same buffer and same file:
1490-byte segments fail four times out of four, 1460-byte segments succeed,
745-byte segments succeed. Linux halves its segments against a window this
small, so only macOS on a jumbo link ever produces a full-size segment - which
is why the failure hides so well.

Where exactly the full segment breaks the stream is not pinned down yet; until
it is, the advertised MSS stays a step below the edge.

* uip: size the buffer to the largest frame the CPU port accepts

UIP_TCP_MSS derives from UIP_CONF_BUFFER_SIZE, and the buffer was large enough
for frames the hardware will never deliver, so the switch advertised a segment
size no peer could usefully reach. A client on a jumbo-MTU link took it at its
word and the oversized replies went nowhere.

Size the buffer to the ingress limit instead. ICMP bypasses MSS and so probes
the hardware directly: on a SWTGW218AS a 1502-byte payload is answered and 1503
never arrives, which puts the largest frame the NIC hands us at 1556 bytes of
uip_buf. UIP_TCP_MSS then derives to 1490, the same edge measured over TCP.

Frames above the limit are dropped by the NIC rather than written to the
buffer - an 8 kB ping leaves the switch untouched - so nothing overruns it.
Frees 644 bytes of XDATA.

* uip: trim these comments, one of which had stopped being true

The note above UIP_CONF_BUFFER_SIZE claimed the MSS derives from it as 1490.
It does not: the commit that follows pins the MSS at 1460 on purpose, a step
below that ceiling, because a segment filling the buffer to its last byte
corrupts large uploads. Left as it was, the file argued with itself.

Both blocks are shorter now. What justifies the numbers stays, which is the
ICMP measurement behind 1556 and the four-out-of-four failure behind 1460.
What went is the storytelling around them, which belongs in this thread rather
than in a config header.

* uip: derive the MSS from the buffer again, minus explicit headroom

The review asked why the buffer size and the MSS are both set by hand when one
used to follow from the other. They answer different questions, but the gap
between them is a number in its own right, so it gets a name now:
UIP_CONF_BUFFER_EXTRA, and UIP_TCP_MSS goes back to being derived.

The headroom is where the measurement lives. A segment that fills uip_buf to
its last byte corrupts large uploads: with nothing but the segment size
changing, 1490 fails four times out of four and 1460 succeeds. With the buffer
sized to the frame the NIC accepts, an extra of 30 lands on 1460.

Deriving it the other way round does not work. Sizing the buffer from a 1460
byte MSS gives 1526, which is 30 bytes under the frame the NIC actually
delivers. A 1502 byte ICMP payload occupies 1556 bytes of uip_buf and is
answered today, and it would stop fitting.

The generated image is byte for byte the same as the one with 1460 written out,
so the expression lands on the value that was measured.

---------

Co-authored-by: d00f <tokyusho@chatik.pl>
2026-08-14 06:42:36 +02:00
René van Dorst 0c339e9123 Merge pull request #328 from DrDoof/fix/httpd-get-path-walk
httpd: stop the GET request line walk at the end of the buffer
2026-08-12 09:28:22 +02:00
René van Dorst 75151e0b00 Merge pull request #319 from bloqaudio/pr/cmd-editor-overflow
cmd_editor: fix cmd_buffer overflow and CLI hang on long input
2026-08-12 09:26:17 +02:00
d00f 43845b911b httpd: stop the GET request line walk at the end of the buffer
The POST path tests for a NUL before it looks at a byte. The GET path did not,
and is_separator() counts only space, tab, question mark and equals, so a request
line carrying none of those walks past the end of uip_buf and writes its
terminator into whatever xdata it happens to stop on.

Everything that is not a POST reaches that walk. The pointer advances past the
method before anything checks that the method was GET, so a TLS record sent to
port 80 by a browser trying https first is enough on its own, as is a port
scanner or a malformed line. The stop is wherever the first space, tab, question
mark or equals turns up in memory, which is why the symptoms are erratic.

Two bytes of BANK1. BANK2 and xdata do not move.
2026-08-12 03:22:01 +02:00
bloqaudio 0ed45e0710 cmd_editor: fix cmd_buffer overflow and serial-ring skip on long input
Typing or pasting a line of 128 characters or more into the CLI hangs the
editor and overruns cmd_buffer[128]: the length check did not reserve room
for the terminating NUL written on Enter, and on a full buffer the character
was retried via continue without advancing the serial-ring read pointer at
the bottom of the loop, so input processing never caught up again.

Cap the line at CMD_BUF_SIZE-1 and, when full, drop the character but fall
through to consume the ring byte instead of spinning on it. The functional
change is three lines; the rest of the diff is re-indentation of the
insert-and-echo block (git diff -w shows the minimal form).
2026-08-11 10:26:06 -05:00
logicog 5e30f8e6c0 Merge pull request #318 from bloqaudio/pr/mac-from-flash
machine: optionally read the factory MAC from flash
2026-08-11 14:32:32 +02:00
logicog 7f3c91ac0c Merge pull request #321 from bloqaudio/pr/dhcp-hostname-opt12
dhcp: announce the hostname via option 12
2026-08-11 14:30:21 +02:00
bloqaudio 3853a2701d machine: read a factory MAC from flash via mac_flash_offset
Adds an optional per-board mac_flash_offset. When non-zero, RTLPlayground
reads a 6-byte MAC from that flash address and uses it if it is a valid
globally-administered unicast address; otherwise it falls back to the
generated locally-administered MAC. Offset 0 (default for every board)
preserves current behaviour.

Enable it for the SWTGW218AS, whose stock firmware keeps the factory MAC
in nvcfg at 0x1FC000. RTLPlayground images and web upgrades only touch
flash below that region, so the factory MAC survives flashing.
2026-08-11 02:32:31 -05:00
bloqaudio 00a5f81871 dhcp: announce the hostname via option 12
Send the configured hostname on discover and request so the DHCP server
can register the device (e.g. in local DNS). Skipped while the hostname
is still empty, i.e. before set_hostname_default() has run.
2026-08-11 02:03:11 -05:00
logicog 53d865059d Merge pull request #320 from bloqaudio/pr/reproducible-build
makefile: derive BUILD_DATE deterministically
2026-08-11 08:50:05 +02:00
logicog b90aedd8f2 Merge pull request #316 from bloqaudio/pr/igmp-memset-cast
rtl837x_igmp: cast &entry to __xdata pointer in memset()
2026-08-11 08:43:28 +02:00
bloqaudio 6b190eb9f3 makefile: derive BUILD_DATE deterministically
Honor SOURCE_DATE_EPOCH, falling back to the HEAD commit date and then
to wall-clock when git is absent. Same-commit builds become
byte-identical (BUILD_DATE is baked into the image and covered by the
trailing CRC), which makes it possible to verify that a binary matches
a source tree.
2026-08-10 15:55:08 -05:00
bloqaudio fae16dabf5 rtl837x_igmp: cast &entry to __xdata pointer in memset()
SDCC rejects memset() on the generic &entry pointer (error 78,
incompatible types). The ipmc/l2mc table entries are __xdata; pass an
explicit (__xdata uint8_t *) so the call resolves.
2026-08-10 15:54:08 -05:00
René van Dorst 52692d05b1 Merge pull request #313 from DrDoof/fix/vlan-id-range
Reject VLAN IDs and numeric arguments the hardware cannot take
2026-08-08 20:51:43 +00:00
René van Dorst d364bd69e4 Merge pull request #306 from DrDoof/mgmt-vlan-gui
system: show and set the management VLAN in System Settings
2026-08-08 20:44:07 +00:00
René van Dorst 70ab7ff769 Merge pull request #307 from DrDoof/l2-sort-filter
l2: fix the CPU/SFP port label, add sorting and filtering
2026-08-08 20:29:50 +00:00
d00f e30976be76 httpd: move the management VLAN into /vlanlist
Review feedback on #306. The value used to ride in /information.json and
the picker fetched /vlanlist separately, so the page needed both requests
to mean anything. /vlanlist now answers {"mgmt":N,"vlan":[...]} and the
picker reads both from the one response. Both consumers in vlan.js were
taught the new shape, and /information.json no longer carries mgmt_vlan.

The truncation guard now reserves 141 bytes instead of 139: the closing
grew to two bytes with the wrapping object, and the comma in front of a
non-first entry was never counted, so the worst case could land one byte
past outbuf even before this change. char_to_html() does not check.

The comments added on this branch are gone as well, style.css and
system.js both, since these files are served byte for byte.

page_impl.rel stays at DSEG 5, OSEG 0, BSEG 3 and the image reports the
same 10183 bytes of XDATA before and after.
2026-08-08 18:26:49 +02:00
d00f 1a55a134c7 system: pick the management VLAN from System Settings
`vlan <id> mgmt` existed on the CLI, but nothing reported which VLAN
currently carries management, so the setting was invisible from the web UI -
the only way to find out was to read the startup config.

Report it in information.json and offer it where the other switch-wide
addressing settings live, between Gateway and Language, as a picker filled
from the configured VLANs. When management is untagged there is no VLAN to
select, so that state shows as a disabled entry rather than inventing an id
the switch would reject.

Confirm before applying, and say what will happen rather than echoing the
action back: the switch starts tagging its own frames, and if the port you
came in on does not carry that VLAN the page becomes unreachable and the way
back is the console. Cancelling puts the picker back where it was.
2026-08-08 18:26:49 +02:00
d00f 23f0ba995c css: align read-only values with the input fields
The System Settings rows all pair a label with a form control, except Model,
which is a bare span. The inputs carry padding and a left margin, so their
text starts about 32px further in than the model name did - close enough to
look like a mistake and far enough to see it.

Give read-only values a class with the same metrics instead of styling the
model specifically; any other value shown without a control gets the
alignment for free.
2026-08-08 18:26:15 +02:00
d00f 22a09bd06a l2: drop the comments and define the column list once
Review feedback on #307: these files are served from flash byte for byte,
so comments ride along on every page load. The three added in this branch
are gone, 357 bytes across l2.js and style.css. The column list existed in
three copies inside renderL2() and is now a single const.
2026-08-08 17:55:27 +02:00
d00f 930a08ec52 cmd: validate the port argument of mtu and pvid
Both handlers turned the first character of the port word into an index
with no check at all. "mtu 0 100" computes '0' - '1' = 255, reads far past
the end of phys_to_log_port[9], and writes the size to whatever register
0x1250 plus that garbage points at. "pvid 0 2" walks the same path into
port_pvid_set(). lag and vlan already validate their port arguments; these
two just did not.

The port now has to be a single digit 1 to 9, which is exactly the range
the mapping table holds. A second digit or a stray letter falls to the
usage message.

"mtu show" also gained the return it was missing: after printing the table
it fell through, derived a port from the word "show" and printed the
garbage byte before the length check stopped it.
2026-08-08 17:46:24 +02:00
d00f 1701d4dc53 cmd: refuse a management VLAN that cannot exist
"vlan 5000 mgmt" parked the management interface on a VLAN the table
cannot hold, which quietly cuts management off. IDs above 4094 now fall
through to the usage message. 0 still switches the management VLAN off,
which is the documented way to disable it.
2026-08-08 17:38:10 +02:00
d00f 1e19a9abe2 cmd: bound the PVID and say something when it is refused
"pvid 1 5000" packed 5000 into the 12-bit PVID field and truncated on the
way, so the port ended up with a PVID nobody chose. 0 and anything above
4094 are refused now, matching what the VLAN table can hold. A failed
parse used to be dropped without a word; both cases print the usage line.
2026-08-08 17:38:10 +02:00
d00f 1098e73337 cmd: stop "mtu" from acting on a value it failed to parse
The handler threw away atoi_short()'s return and leant on the range test
alone. The range test cannot tell a failed parse from a small number, so
"mtu 1 99999" stopped at the partial 9999 and went through as a number
nobody typed. With the parse result checked, a failure is rejected with
the same message as an out-of-range value.
2026-08-08 17:38:10 +02:00
d00f 99b0fc4b32 cmd: give mtu a lower bound as well as an upper one
"mtu 1 0" was accepted. The chip takes it verbatim, the port reports 0x0000
back, and it stops passing frames: on a live 2.5G LAG member the LACPDU
receives moved by 4 in fifteen seconds against 21 on the sibling port, and
the partner went expired. Restoring the size brought both back.

Nothing shorter than a minimum Ethernet frame is a usable maximum, so the
range is now 64 to 16383. The upper end is unchanged and still comes from
the width of the field the value is written into.

This also covers most of #312 by accident: "mtu 1 abc" leaves the parse
result at 0 and now gets rejected on the bound rather than reaching the
register. It does not cover all of it. The handler still ignores what
atoi_short() returns, so "mtu 1 99999" stops on a partial 9999 and goes
through as a number nobody typed.

The GUI is not affected either way, it offers a fixed list of sizes.
2026-08-08 17:30:25 +02:00
d00f 10d472d8a6 vlan: reject VLAN IDs the table cannot hold
vlan_create() and vlan_delete() wrote the ID straight into the table index
register. vlan_get() has refused anything >= 0xfff for a while, so reads were
guarded and writes were not: "vlan 4095 1 2" built an entry that no read path
can see, and IDs above that either miss the table or alias onto another VLAN.

Both writers now enforce the range vlan_get() already did, and parse_vlan()
rejects the same values with the usage message so the CLI says why. The check
sits after the "vlan 0 mgmt" branch, which legitimately takes 0 to switch the
management VLAN off.

Costs nothing in RAM: rtl837x_port.rel stays at DSEG 0, OSEG 5, and the image
still reports 10207 bytes of XDATA in use.
2026-08-08 17:13:28 +02:00
d00f 6254f44001 cmd: reject out-of-range numeric arguments instead of wrapping
atoi_short() accumulated into a uint16_t without checking, so "vlan 65540"
wrapped to 4 and edited VLAN 4 instead of failing. atoi_byte() had the same
hole with "300" landing on 44. Both now refuse the digit that would push the
value past its type, before it lands.

The partial result is deliberately left alone rather than zeroed. Zeroing
would give the function one tidy rule, every failure leaves 0, but a caller
that ignores the return would then write that 0, and 0 is not a harmless
number everywhere. Set as a port MTU it stops the port taking frames: I put
0 on a live 2.5G LAG member and its LACPDU receives moved by 4 in fifteen
seconds against 21 on the sibling port, with the partner going expired.
Putting the size back recovered both. A wrong number does less damage than
that, and the real fix belongs in the callers that ignore the return anyway.

The test sits inside the loop rather than after it, so no wider accumulator
is needed and the parser stays off the internal RAM budget.

056a30a on the branch in #303 fixes atoi_byte a different way, by widening
the accumulator. Whichever lands first, the other hunk should go.
2026-08-08 17:13:08 +02:00
René van Dorst c435838376 Merge pull request #308 from plaes/horaco-zx-sg4t2
docs: Add Horaco XZ-SG4T2 skeleton documentation and images
2026-08-08 12:08:27 +00:00
René van Dorst 3f7db7e430 Merge pull request #297 from DrDoof/hostname
system: configurable hostname + show model on the System page
2026-08-07 18:10:34 +00:00
René van Dorst f98fe32d0c Merge pull request #309 from plaes/http-security-fixes
httpd: Fix buffer offerflow in scan_header
2026-08-07 10:08:11 +00:00
Priit Laes d2e8ee0e41 httpd: Fix buffer offerflow in scan_header
Fixes #300
2026-08-06 15:24:09 +03:00
Priit Laes a9a455e447 docs: Add Puya P25D40SH flash chip to list of known supported 2026-08-06 10:57:49 +03:00
Priit Laes 3f26bf7348 docs: Use relative paths 2026-08-06 10:57:49 +03:00
Priit Laes ac302d4a67 Add barebones docs for already supported Horaco ZX-SG4T2 / WTG024AS-A-V2.0.1_4C_2SFP 2026-08-06 10:47:35 +03:00
Priit Laes 409bc58e17 docs: Add HC-SWTGW215AS to supported devices index 2026-08-06 10:13:29 +03:00
Priit Laes fbcec2cb5a docs: Fix capitalization/typos in company names 2026-08-06 10:06:24 +03:00
d00f ccb90ed16f l2: refresh the type column when a row is reused
The paint loop reuses existing rows and rewrites port, MAC, VLAN and the
delete button, but never the type cell - only the insert path set it. With a
fixed row order that stayed invisible, since a row usually landed back where
it was. Sorting moves rows, so every other column followed the data while
type kept the previous row's value, which made sorting by type look broken
when the sort itself was correct.
2026-08-05 21:42:54 +02:00
d00f 4389cfcd8b l2: show which column sorts and in which direction
Clicking a heading sorted the table but nothing said so afterwards - the
only feedback was the rows moving, which is no help when the sort key is a
column you are not looking at.

Give every sortable heading a permanent marker: a neutral double arrow when
it is not the sort key, up or down when it is. The marker sits in its own
span so the i18n pass, which replaces the heading text, does not wipe it.
2026-08-05 21:35:54 +02:00
d00f 5e901fb5c2 l2: sort and filter the forwarding table from its header
The table lists every learned and static entry in one flat block, which is
fine with a handful and unusable with a few hundred: finding out where one
MAC sits, or what a port has learned, meant reading the whole thing.

Make the column headings sort and give each one a filter box. Filters are
substring matches combined with AND, so "port 8 + static" is two keystrokes.
A counter above the table shows matched out of total, so a filter that hides
everything is obvious rather than looking like an empty table.

Sorting keeps the CPU entry from comparing as a number - it sorts last
instead of landing between ports 8 and 9, where a string-vs-number compare
would otherwise put it.

The filter inputs live inside the existing header cells rather than in a
second row: the paint loop addresses data rows as rows[i+1], and a second
header row would have shifted every one of them.
2026-08-05 21:21:03 +02:00
d00f 2974e4b66a l2: stop labelling the SFP port as CPU
The port column was mapped from logical to physical numbering first and the
CPU label applied afterwards, testing for port 9. On an 8+1 board the SFP
port maps to physical 9 as well, so every entry learned on the SFP was shown
as CPU - on this switch that was 26 of 30 entries.

Label the CPU while the number is still logical, before the mapping, so the
two cannot collide.
2026-08-05 21:21:03 +02:00
d00f 108ac9b8fc system: derive the default hostname after the startup config
Move the MAC-derived default name out of main() into its own function and
call it after execute_config(), returning early when the config already
set a name - a configured switch then does no work for it at all.

The body deliberately has no local variables. Locals here - 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 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, since the
overlay is shared across the whole image, and hoisting the locals to xdata
does not either, because itohex() is inline and brings its own frame. This
only shows up in an integrated build; the branch on its own links fine.

Suggested-by: vDorst
2026-08-05 16:48:36 +02:00
René van Dorst de3eca26c3 Merge pull request #301 from zytstudio/MACHINE_FG_4GT_2SX_V2_0
Add FG-4GT-2SX_V2.0
2026-08-05 08:53:02 +00:00
ZYT 8164166465 Fix mistake in comments 2026-08-05 11:15:48 +08:00
ZYT 87fe1e77ae Merge branch 'main' into MACHINE_FG_4GT_2SX_V2_0 2026-08-05 11:09:53 +08:00
ZYT a753f6f02e Add led colors and remove an unnecessary photo 2026-08-05 11:07:51 +08:00
René van Dorst c86d4b31f2 Merge pull request #304 from plaes/makefile
makefile: Make CC and ASM overridable via command line
2026-08-04 19:50:31 +00:00
Priit Laes 3c89b22207 makefile: Use origin check for CC variable
Make defines defaults for commonly used variables, therefore
we need to check first how the variable was defined. If `default`
value was used, override it with our own default.
2026-08-04 21:55:28 +03:00
Priit Laes bebe79c4c9 makefile: Fix possible shell injections in makefile 2026-08-04 18:53:23 +03:00
Priit Laes adb0b54691 makefile: Reorder sources to immediately fail on invalid MACHINE
Also group and reorder source files and move each of these to
separate line.
2026-08-04 17:57:53 +03:00
Priit Laes b99436543a makefile: Make CC and ASM overridable via command line
This makes it easier to run make without docker within
distros without editing Makefile. For example Fedora:
`ASM=sdcc-sdas8051 CC=sdcc-sdcc make`
2026-08-04 13:17:53 +03:00
d00f 1194823cd5 system: drop the defensive checks around the System page fields
The hostname and model fields were populated through element-existence
checks and empty-string fallbacks that the other fields on the page do
without. Assign them the same way.

Suggested-by: vDorst
2026-08-04 05:43:05 +02:00
d00f 94716ceb5d system: derive the default hostname from the MAC address
Every switch came up as "RTLPlayground", so several of them on one
network were indistinguishable until someone configured a name. Append
the last three MAC octets (e.g. RTLPlayground-1ef924) - unique in
practice, still recognisable, and any "hostname ..." line in the startup
config overrides it as before.

Suggested-by: plaes
2026-08-04 05:43:05 +02:00
d00f 13120127df cmd: make "hostname" report the name and reject stray arguments
Typing "hostname" on its own cleared the name: with no argument the copy
loop never ran and the terminating NUL landed at index 0. Report the
current name instead, accept exactly one argument to set it, and reject
anything longer - a name with spaces tokenizes into several words, and
silently keeping only the first one is worse than an error. Walk the
buffer with a pointer, which the compiler codes better than indexing.

Suggested-by: vDorst
2026-08-04 05:43:05 +02:00
ZYT 46037cd54d Add FG-4GT-2SX_V2.0 2026-08-02 06:46:52 +08:00
d00f e6cd362a1e system: show the hardware model on the System page
machine.machine_name is already reported in /information.json as hw_ver;
surface it read-only on the System Settings page so the exact build
target is visible in the UI, without adding a redundant JSON field.
2026-07-25 13:21:13 +02:00
d00f f21b3a32bd system: configurable hostname (device identity)
Add a device hostname settable from the CLI (`hostname <text>`) and the
System Settings page. The value is sanitized on ingest to JSON-safe
printable ASCII (<=23 chars), stored in a shared __xdata buffer, seeded
to "RTLPlayground" at boot, persisted through the startup-config, and
reported in /information.json. It lives in the common header so other
modules can advertise it (LLDP uses it as the System Name TLV).
2026-07-25 13:07:09 +02:00
logicog 35941dd19f Merge pull request #291 from DrDoof/fix/webui-login-cookies
httpd: fix browser login (cookie parsed by name, Connection: close, self-contained login page)
2026-07-25 11:09:19 +02:00
d00f 0f257d38df httpd: shorten comments per review
Keep only the non-obvious bits; the rationale for each change already
lives in the respective commit messages.
2026-07-25 10:39:32 +02:00
logicog 912e68d6eb Merge pull request #295 from sempr/main
Fix rate range for SDS_10GR return value
2026-07-25 08:21:53 +02:00
Sempr c473646389 Fix rate range for SDS_10GR return value
Broaden range of SFP+ rates mapped to SDS_10GR
A commonly used SFP+ form-factor 10GBASE-LR (Hisense LTF1303-BH+) reports 0x62(	BR Nominal: 9800MBd) at
power on and then switches to 0x64 after booting up.

0x62 -> 9.8gbps
0x63 -> 9.9gbps
0x64 -> 10gbps
0x6f -> 11.1gbps
0x70 -> 11.2gbps

These are outside of the previous 0x63-0x6f range which prevented the link from ever being brought up. So I changed the range to 0x62-0x6f
2026-07-24 16:02:20 +08:00
logicog 51cf40d040 Merge pull request #290 from eraiza0816/feat/dockerfile
add: docker use prerequisites
2026-07-24 07:58:12 +02:00
d00f 3fd9cdfa7e httpd: parse the session cookie by name, not fixed offset
Root cause of the "browser login always bounces back with Wrong password!
while curl works": scan_header() read the session id from a fixed offset
into the Cookie header (p + 17), assuming "session=" is the first and only
cookie. Browsers keep stale cookies for a long time - e.g. an "admin" cookie
left over from this switch's VENDOR firmware - so the header can arrive as
"Cookie: admin=..; session=..", the fixed offset then points into the admin
value, authentication silently fails and every page bounces to login although
the password had been accepted. curl sends only "session=", which is why
command-line tests passed while a real browser (with that stale cookie) failed.

- scan_header(): scan the Cookie header for the actual "session=" key
  (matched as "session" - is_word() requires a separator after the pattern
  and '=' is on its list, the first value byte is not).
- is_word_x(): accept ';' as a terminating separator so the session value
  also matches when it is not the last cookie in the header.

Verified on hardware end-to-end in a real browser WITH the stale "admin"
cookie present: login -> index.html, all pages and JSON endpoints work.
2026-07-21 06:48:18 +02:00
d00f 3189820ba0 httpd: send Connection: close (single-connection uIP mitigation)
Real root cause of the "web login fails from a browser but works from curl":
the uIP httpd is built with UIP_CONF_MAX_CONNECTIONS = 1 and uses global
response state (outbuf/slen/session), i.e. it serves exactly one TCP connection
at a time and closes it after each response - but never advertises that via
the Connection header. A browser's HTTP/1.1 client therefore assumes the
connection may be persistent and can park it in its keep-alive pool for reuse;
a later request sent on that pooled connection hits one the server has already
closed, and a POST (unlike a GET) is never retried by the browser, so it can
be silently lost this way.

This adds "Connection: close" to every response so the browser does not pool
and reuse a connection the server is about to drop. On its own this did not
fully explain the reported login failures - the actual authentication bug is
fixed in the next commit (the Cookie header parsed at a fixed offset) - but it
is correct behaviour for a server that only ever handles one connection, and
removes one source of dropped requests.
2026-07-21 06:47:44 +02:00
d00f ed74ec1e97 httpd/login: complete CSP + password autocomplete hint
Two hygiene fixes for the web UI, prompted by a login that appeared to fail
under privacy shields (Brave Shields / NoScript-family extensions):

- httpd: replace the partial "style-src 'self' 'unsafe-inline'" CSP with a
  complete, first-party policy (default-src 'self'; script-src 'self'
  'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;
  connect-src 'self'; form-action 'self'). Everything the UI needs is
  same-origin; the explicit policy stops shields injecting their own
  restrictive report-only probes (the noisy script-src-elem 'none' console
  spam) and passes a strict-CSP audit. Verified: no CSP violations in-browser.
- login.html: add autocomplete="current-password" so password managers
  recognise the field (they showed "unknown password" without it).

NOTE: these do NOT bypass a browser's LAN-device protection (NoScript "lan" /
Brave Shields), which strips the POST body of requests to a LAN address and is
why the login can fail in-browser while the same credentials work over curl.
That is a deliberate browser security feature; the user must allow the site in
their shields to log in. The backend password (default 1234) is unchanged and
correct.
2026-07-21 06:47:10 +02:00
Your Name dd95a05487 add: docker use prerequisites 2026-07-19 19:49:30 +09:00
logicog 481c02c740 Merge pull request #289 from eraiza0816/fix/httpd-sim-const-warning
fix: httpd-sim-const-warning
2026-07-19 09:49:08 +02:00
Your Name 0a31a65477 fix: httpd_sim 2026-07-19 16:44:21 +09:00
Lynn-Becky 2d176b7ccc add SWTG024AS-A-V2.0.1 with 5 RJ45 port (#282)
* add MACHINE_SWTG024AS_A_2_0_1_5_RJ45

* fix(machine): update SFP port LED color descriptions for PCB-SWTG024AS V2.0

* add(doc): create a simple documentation for SWTG024AS-A-V2.0.1_5_RJ45

* refactor(machine): update machine definition
2026-07-18 06:20:47 +02:00
logicog a1fac63710 Merge pull request #286 from eraiza0816/feature/language-settings
add language selection to system settings
2026-07-18 06:18:33 +02:00
logicog 5c7f82e2fe Merge pull request #285 from eraiza0816/feat/dockerfile
feat: add Dockerfile for development environment
2026-07-18 05:58:10 +02:00
Your Name e765d17c2b feat: add language selection to system settings
Add a language selector to the System Settings page supporting English,
Japanese, and Chinese. The i18n dictionary already contained zh
translations but lacked the UI to switch languages.

Changes:
- html/i18n.js: add sys_language key to en/ja/zh
- html/system.html: add language selector dropdown
- html/system.js: add changeLang() and selector initialization
- tools/httpd_sim.c: fix cookie parsing for multi-cookie headers;
  fix Set-Cookie response (missing Path=/, missing \r\n\r\n);
  add SO_REUSEADDR for faster port reuse
2026-07-15 01:35:10 +09:00
Your Name 8fb9afb955 feat: add Dockerfile for development environment
- Debian 13 (trixie) based with sdcc 4.5.0 from apt
- Includes gcc, make, xxd, python3, libjson-c-dev, golang-go
- Add .dockerignore to exclude build artifacts
- Add Docker usage section to README (collapsible)
2026-07-14 02:10:55 +09:00
logicog e96780fadb Merge pull request #280 from tofurky/ddm_broken_module
Add SFP quirk for devices that misreport DDM capability
2026-07-13 09:57:35 +02:00
Matt Merhar 2221f0fa32 Add SFP quirk for devices that misreport DDM capability
On a QSFPTEK QT-SFP+-T (RTL8261C) 10GBase-T module, the diag type field
(92) comes back as 0x00, but there's actually some statistics available
like temperature. Other metrics may be hard-coded values.

Add a basic struct that allows matching on vendor and/or model, using a
bitfield to allow multiple quirks for a given SFP module. Only
SFP_QUIRK_DDM is implemented.

For modules matching SFP_QUIRK_DDM, attempt an I2C read of the MSB of
module voltage during probe if DDM is "unsupported" - if it's not 0xff,
override the reported options so we can pull the diagnostic data.

To allow simpler comparisons, convert the ASCII fields (vendor,
model, serial) from space-padded to standard NULL-terminated strings.

strcmp() is moved from httpd.c to rtlplayground.c alongside other string
functions and shared between them.

The __reentrant keyword is used for the new functions to avoid using up
additional OSEG space. This allocates the variables on the stack, which
is OK for this particular code path.

The JSON assembly in send_status() is slightly modified to treat the
sfp_module_* data as standard NULL-terminated strings, and a repeated
subtraction was moved into a uint8_t to declutter the code.
2026-07-13 01:31:29 -04:00
logicog f86b8f32de Merge pull request #283 from Lynn-Becky/translate/chinese
Translate to Chinese
2026-07-13 07:15:52 +02:00
logicog 9389db9e9b Merge pull request #284 from eraiza0816/feature-translate
fix: rename local variable to avoid shadowing global translation
2026-07-13 07:15:29 +02:00
Your Name 519c3a8df3 fix: rename local variable to avoid shadowing global translation function 2026-07-10 13:09:41 +09:00
Lynn-Becky 64422367d5 feat(translate):translate to Chinese 2026-07-08 20:40:00 +08:00
logicog dc2b03e331 Merge pull request #279 from eraiza0816/translate-japanese
Support i18n & translate japanese
2026-07-07 19:28:19 +02:00
Your Name fcf1b3c310 Fix: Remove the language-specific display logic. 2026-07-05 02:31:37 +09:00
Your Name 1dd6156a1f add: how to support i18n 2026-07-05 01:09:50 +09:00
Your Name e75a4c2a95 support i18n & translate japanese 2026-07-05 00:51:27 +09:00
René van Dorst 05d8c36afe Merge pull request #278 from eraiza0816/update-docs-SWTG024AS
update document: SKS3200M-4GPY2XF flash size
2026-07-04 12:47:04 +00:00
René van Dorst b6c9eaf00f Merge pull request #275 from riker77/mokerlink-2G040210GSM
Add Mokerlink 2G040210GSM (2M-PCB43-V1.1)
2026-07-04 12:45:47 +00:00
Your Name ecf5f47960 update SKS3200M-4GPY2XF flash size 2026-07-04 15:52:25 +09:00
Ron ebf6ed7526 Update 2M-PCB43-V1.1.md
Add hint to use KP_9000_6XHML_X2 as a compatible machine definition.
2026-06-29 20:30:07 +02:00
riker77 96f2c860fc Add Mokerlink 2G040210GSM (2M-PCB43-V1.1) 2026-06-29 17:57:09 +02:00
feelfree69 b80d46955c Merge pull request #274 from Lynn-Becky/feat/PCB-SWTG024AS-V2.0
fix(machine): move SWTG024AS-specific SDS0 setup to machine.c
2026-06-28 21:32:53 +02:00
Lynn-Becky 0a18c64dcc fix(machine): move SWTG024AS-specific SDS0 setup to machine.c 2026-06-28 22:47:33 +08:00
René van Dorst b53c56e7f6 Merge pull request #273 from Lynn-Becky/feat/PCB-SWTG024AS-V2.0
fix(machine): SWTG024AS V2.0 fix 5th RJ45 port
2026-06-27 19:50:05 +00:00
Lynn-Becky 17d41b53b7 update doc 2026-06-28 00:56:50 +08:00
Lynn-Becky bd98cb0312 fix(machine): SWTG024AS V2.0 fix 5th RJ45 port 2026-06-28 00:48:05 +08:00
René van Dorst 5566d71522 Merge pull request #272 from Lynn-Becky/feat/PCB-SWTG024AS-V2.0
fix(SWTG024AS-V2.0): correct SFP port GPIO assignments
2026-06-27 13:33:29 +00:00
Lynn-Becky 5e1396cc42 fix(machine): correct SFP port GPIO assignments 2026-06-27 21:16:47 +08:00
René van Dorst 7fe0678c92 Merge pull request #271 from th0m4sek/main
Add reset button gpio to SWTG018AS-A V2.0
2026-06-25 18:33:18 +00:00
th0m4sek 7ec25a39a2 Add brands information for SWTG018AS-A V2.0
Added brands section with details for Ampcom and Horaco.
2026-06-25 20:23:54 +02:00
th0m4sek 98e3cde2f5 Update reset_pin on SWTG018AS-A V2.0 2026-06-25 20:16:56 +02:00
René van Dorst d90c02f951 Merge pull request #268 from th0m4sek/main
Table with supported devices
2026-06-23 17:40:51 +00:00
th0m4sek bbb92c86db Fix formatting issue in supported_devices.md 2026-06-23 09:16:05 +02:00
René van Dorst 1635236a05 Merge pull request #266 from hlyi/pr-tool
Tools for detecting GPIO mapping and extracting LED configure
2026-06-23 06:28:38 +00:00
th0m4sek 5e3a280b5c Update supported devices list with new entries 2026-06-23 07:26:35 +02:00
HL Yi a56e2a419b sleep-interval default value is reduced to 1.
i2c-bus is required argument
replace --bus with --i2c-bus for i2c_dump_rtl_regs.py for better consistency
format lint clean up
2026-06-22 18:02:34 -05:00
th0m4sek 9c01227572 Fix typo in device manufacturer name from 'Haraco' to 'Horaco' 2026-06-22 23:21:13 +02:00
th0m4sek 6d37a47481 Rename Horaco SWTGW215AS to HC-SWTGW218AS 2026-06-22 23:18:42 +02:00
th0m4sek 87b9e59cdb Update ZX-SWTG124AS entry with new data 2026-06-22 22:47:02 +02:00
th0m4sek 0e86b84efa Add Ports column to supported devices list
Updated the supported devices table to include Ports column and adjusted device details.
2026-06-22 22:45:20 +02:00
René van Dorst bd595b7f94 Merge pull request #263 from Lynn-Becky/feat/PCB-SWTG024AS-V2.0
Add PCB-SWTG024AS V2.0 (Unmanaged)
2026-06-22 19:27:38 +00:00
Lynn-Becky 06d4a3504a Add LED status comments,try to fix 5th rj45 2026-06-23 00:42:31 +08:00
Lynn-Becky d68534e833 update PCB-SWTG024AS-V2.0 to SWTG024AS-V2.0 2026-06-22 23:51:57 +08:00
Lynn-Becky e0356bd751 Add SWTG024AS-V2.0 doc and photo 2026-06-22 22:24:46 +08:00
HL Yi 4b770a8abc Fixed document based upon vDorst
Expand ignore IO list to the GPIOs related to SYS_LED, UART, SMI, and SPI
2026-06-22 07:08:19 -05:00
th0m4sek 709436ca13 Update supported devices list
Added new device 'Keeplink KP-9000-6XH-X2' and removed duplicate 'Horaco SWTGW215AS' entry.
2026-06-22 12:08:52 +02:00
th0m4sek 28856ef495 Revise supported devices documentation
Updated the supported devices list with new entries and details.
2026-06-22 12:07:30 +02:00
th0m4sek 113bb654aa Add links to PCB documentation for supported devices
Updated device entries with links to detailed PCB documentation.
2026-06-22 11:14:04 +02:00
th0m4sek efc1883685 Table with supported devices
Added a table listing supported hardware devices with details.
2026-06-22 10:38:40 +02:00
feelfree69 72921ba3be Merge pull request #267 from hlyi/pr-doc
add SKS3200-8E1X to SWTGW218AS.md doc
2026-06-22 08:00:46 +02:00
HL Yi 70be673fca updates based upon vDorst's feedback.
change i2c_read_rtl_gpio sleep tiemr to 1s
update README.md
Sorted port speed
2026-06-22 00:18:32 -05:00
HL Yi bf5205258e add SKS3200-8E1X to SWTGW218AS.md doc 2026-06-21 17:24:02 -05:00
René van Dorst 98ddb0bb29 Merge pull request #253 from Erdnusschokolade/fix/vlan-mgmt-filter
Fix conf_overwrite filter removing vlan mgmt entries
2026-06-21 18:52:59 +00:00
HL Yi d152f89549 remove redundant code 2026-06-21 13:22:54 -05:00
HL Yi c61e94527e add clarification for firmware requirement 2026-06-21 13:21:06 -05:00
HL Yi f76ccaf3d8 add disclaim that tool tested in linux 2026-06-21 13:08:58 -05:00
HL Yi ebc6cd06ad add utilities for led and gpio dump and detection 2026-06-21 13:04:04 -05:00
Lynn-Becky 017aaf71d6 Add PCB-SWTG024AS V2.0 machine target 2026-06-21 01:15:34 +08:00
Erdnusschokolade 4eaf16f96d Drop dead exact-match check in vlan delete filter
A bare "vlan N" line never matches conf_cmds (a VLAN entry always carries
a member port or the mgmt/name keyword), so it is never stored. The
c !== "vlan N" comparison could therefore never match an entry; the
startsWith(prefix) check alone covers all stored vlan lines.
2026-06-20 08:13:43 +02:00
Erdnusschokolade 718745e7b5 Reduce stale management VLAN entries on config save
The firmware has a single management VLAN (one management_vlan variable),
so a stored config should never carry more than one "vlan N mgmt" line.
When parsing a mgmt command, drop any previously stored mgmt entry so
repeatedly changing the management VLAN no longer accumulates stale lines.
2026-06-20 08:00:54 +02:00
Erdnusschokolade 06ee1ce38e Persist port speed settings in config parser
parseConf() only recognized "port N name" in conf_cmds, so "port N <speed>"
lines were treated as unknown commands and dropped on save - configured port
speeds never persisted.

Add the speed command (incl. optional half/full duplex suffix) to conf_cmds,
add a "port N" entry to conf_overwrite so re-saving a speed replaces the
previous value, and guard the per-port name entry so changing a port's speed
no longer wipes its configured name.
2026-06-18 14:49:56 +02:00
René van Dorst f122402bdd Merge pull request #259 from logicog/rtl8221b_100m_support
Add support for 100M on RTL8221B ports
2026-06-15 17:54:03 +00:00
Erdnusschokolade 592b903305 Fix conf_overwrite filter removing vlan mgmt entries
The vlan/mgmt lookahead split (commit dcc60c3) correctly separated the
patterns so that `vlan N mgmt` and `vlan N <members>` match different
conf_overwrite entries. But the filter that applies the overwrite still
used `item.startsWith(matchStr + " ")` with matchStr = "vlan N", which
also matches "vlan N mgmt" — so the management entry was removed whenever
the VLAN's membership definition was re-saved.

Example: with both `vlan 44 management 2t 4t 5 6t` and `vlan 44 mgmt`
in the config, changing the membership dropped `vlan 44 mgmt` entirely.

Added a `!item.endsWith(" mgmt")` guard so management entries survive the
filter. No reordering is needed: `vlan N mgmt` only sets the global
management_vlan variable (cmd_parser.c) and does not depend on the VLAN
table entry existing, so its position in the config is irrelevant.
2026-06-02 22:52:06 +02:00
74 changed files with 2455 additions and 358 deletions
+19
View File
@@ -0,0 +1,19 @@
.git/
.gitignore
.gitattributes
.github/
output/
installer/output/
*.bin
html_data.c
html_data.h
*.o
*.rel
*.lst
*.sym
*.asm
*.ihx
*.img
*.map
*.mem
*.lk
+19
View File
@@ -0,0 +1,19 @@
FROM debian:13-slim
RUN apt-get update && apt-get install -y \
make \
gcc \
sdcc \
xxd \
python3 \
libjson-c-dev \
golang-go \
git \
&& rm -rf /var/lib/apt/lists/*
# git safe.directory for mounted repos (Makefile uses git describe)
RUN git config --global --add safe.directory /workspace
WORKDIR /workspace
CMD ["bash"]
+52 -9
View File
@@ -4,9 +4,11 @@ DEFAULT_CONFIG_LOCATION = 454656
CONFIG_LOCATION = 458752 CONFIG_LOCATION = 458752
HTML_LOCATION = 262144 HTML_LOCATION = 262144
ifeq ($(origin CC),default)
CC = sdcc CC = sdcc
endif
CC_FLAGS = -mmcs51 -I. -Ihttpd -Iuip CC_FLAGS = -mmcs51 -I. -Ihttpd -Iuip
ASM = sdas8051 ASM ?= sdas8051
AFLAGS= -plosgff AFLAGS= -plosgff
SUBDIRS := tools SUBDIRS := tools
@@ -30,17 +32,58 @@ endif
VERSION_EXTENSION = v$(VERSION)-$(GIT_VERSION) VERSION_EXTENSION = v$(VERSION)-$(GIT_VERSION)
FILENAME_EXTENSION = $(VERSION_EXTENSION)-$(MACHINE) FILENAME_EXTENSION = $(VERSION_EXTENSION)-$(MACHINE)
# Deterministic build date: honor SOURCE_DATE_EPOCH, else the HEAD commit date,
# else wall-clock (no-git fallback). Keeps same-commit builds byte-identical
# (BUILD_DATE is baked into the image and covered by the trailing CRC).
SOURCE_DATE_EPOCH ?= $(shell git show -s --format=%ct HEAD 2>/dev/null)
ifeq ($(SOURCE_DATE_EPOCH),)
BUILD_DATE := $(shell date +"%Y-%m-%d %H:%M:%S")
else
BUILD_DATE := $(shell date -u -d @$(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S" 2>/dev/null \
|| date -u -r $(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S")
endif
all: create_build_dir $(VERSION_HEADER) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin all: create_build_dir $(VERSION_HEADER) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin
create_build_dir: create_build_dir:
mkdir -p $(BUILDDIR) mkdir -p "$(BUILDDIR)"
mkdir -p $(BUILDDIR)/uip mkdir -p "$(BUILDDIR)/uip"
mkdir -p $(BUILDDIR)/httpd mkdir -p "$(BUILDDIR)/httpd"
# Keep machine.c in first position to fail immediately on invalid $MACHINE value
SRCS = \
machine.c \
cmd_editor.c \
cmd_parser.c \
dhcp.c \
html_data.c \
rtlplayground.c \
syslog.c \
udp_apps.c
# RTL837x
SRCS += \
rtl837x_bandwidth.c \
rtl837x_flash.c \
rtl837x_igmp.c \
rtl837x_init.c \
rtl837x_leds.c \
rtl837x_phy.c \
rtl837x_pins.c\
rtl837x_port.c \
rtl837x_stp.c
SRCS += \
httpd/httpd.c \
httpd/page_impl.c
SRCS += \
uip/timer.c \
uip/uip.c \
uip/uiplib.c \
uip/uip_arp.c \
uip/uip-fw.c \
uip/uip-neighbor.c \
uip/uip-split.c
SRCS = rtlplayground.c rtl837x_flash.c rtl837x_leds.c rtl837x_phy.c rtl837x_port.c cmd_parser.c html_data.c rtl837x_igmp.c
SRCS += rtl837x_stp.c rtl837x_pins.c dhcp.c machine.c cmd_editor.c rtl837x_bandwidth.c rtl837x_init.c syslog.c
SRCS += uip/timer.c uip/uip.c uip/uip_arp.c uip/uiplib.c uip/uip-fw.c uip/uip-neighbor.c uip/uip-split.c udp_apps.c
SRCS += httpd/httpd.c httpd/page_impl.c
OBJS = ${SRCS:%.c=$(BUILDDIR)/%.rel} OBJS = ${SRCS:%.c=$(BUILDDIR)/%.rel}
DEPS := ${SRCS:%.c=$(BUILDDIR)/%.d} 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')
@@ -52,7 +95,7 @@ $(VERSION_HEADER):
@echo "#ifndef VERSION_H" > $(VERSION_HEADER) @echo "#ifndef VERSION_H" > $(VERSION_HEADER)
@echo "#define VERSION_H" >> $(VERSION_HEADER) @echo "#define VERSION_H" >> $(VERSION_HEADER)
@echo "#define VERSION_SW \"$(VERSION_EXTENSION)\"" >> $(VERSION_HEADER) @echo "#define VERSION_SW \"$(VERSION_EXTENSION)\"" >> $(VERSION_HEADER)
@echo "#define BUILD_DATE \"$(shell date +"%Y-%m-%d %H:%M:%S")\"" >> $(VERSION_HEADER) @echo "#define BUILD_DATE \"$(BUILD_DATE)\"" >> $(VERSION_HEADER)
@echo "#endif" >> $(VERSION_HEADER) @echo "#endif" >> $(VERSION_HEADER)
httpd: html_data.h httpd: html_data.h
+47
View File
@@ -60,6 +60,53 @@ still has an older version of sdcc, but you will need sdcc version 4.5 for the c
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
``` ```
<details>
<summary>If using Docker (click to expand)</summary>
### Prerequisites
Install Docker for your platform:
- **Linux (Debian/Ubuntu)**: `sudo apt install docker.io` then `sudo usermod -aG docker $USER` (log out and back in)
- **Linux (other distros)**: Follow the [Docker Engine install guide](https://docs.docker.com/engine/install/)
- **Windows**: Install [Docker Desktop for Windows](https://docs.docker.com/desktop/setup/install/windows-install/)
- **macOS**: Install [Docker Desktop for Mac](https://docs.docker.com/desktop/setup/install/mac-install/)
### Usage
A Dockerfile is provided for a reproducible build environment:
```
docker build -t rtlplayground-dev .
```
Build the firmware (replace MACHINE with your target, e.g. `DEFAULT_8C_1SFP`):
```
docker run --rm -v $(pwd):/workspace rtlplayground-dev make MACHINE=DEFAULT_8C_1SFP
```
The resulting `.bin` file appears in `output/` on your host.
Build host tools only:
```
docker run --rm -v $(pwd):/workspace rtlplayground-dev make -C tools
```
Run the web-interface simulator locally:
```
docker run --rm -p 8080:8080 -v $(pwd):/workspace rtlplayground-dev \
tools/output/httpd_sim /workspace/html
```
Edit `machine.h` or `config.txt` on your host, then re-run `make` — the
source directory is mounted into the container, so changes take effect
immediately. To build for a different machine, pass `MACHINE=...`.
</details>
## (1) Compiling for direct chip flashing AND upgrading an existing RTLPlayground running device ## (1) Compiling for direct chip flashing AND upgrading an existing RTLPlayground running device
Edit machine.h with an editor like vi or nano. Select the correct machine the firmware should build for. Edit machine.h with an editor like vi or nano. Select the correct machine the firmware should build for.
+5 -2
View File
@@ -40,8 +40,10 @@ void cmd_edit(void) __banked
{ {
while (l != sbuf_ptr) { while (l != sbuf_ptr) {
if (sbuf[l] >= ' ' && sbuf[l] < 127) { // A printable character, copy to command line if (sbuf[l] >= ' ' && sbuf[l] < 127) { // A printable character, copy to command line
if (cmd_line_len >= CMD_BUF_SIZE) // Reserve one byte for the terminating NUL written on Enter. When the
continue; // line is full, drop the character but still fall through to advance the
// serial-ring read pointer below; a 'continue' here would spin forever.
if (cmd_line_len < CMD_BUF_SIZE - 1) {
write_char(sbuf[l]); write_char(sbuf[l]);
// Shift buffer to right // Shift buffer to right
for (uint8_t i = cmd_line_len; i > cursor; i--) for (uint8_t i = cmd_line_len; i > cursor; i--)
@@ -55,6 +57,7 @@ void cmd_edit(void) __banked
// Move backwards // Move backwards
for (uint8_t i = cursor; i < cmd_line_len; i++) for (uint8_t i = cursor; i < cmd_line_len; i++)
write_char('\010'); // BS works like cursor-left write_char('\010'); // BS works like cursor-left
}
} else if (sbuf[l] == '\033') { // ESC-Sequence } else if (sbuf[l] == '\033') { // ESC-Sequence
// Wait until we have at least 3 characters including the ESC character in the serial buffer // Wait until we have at least 3 characters including the ESC character in the serial buffer
if (((sbuf_ptr + SBUF_SIZE - l) & SBUF_MASK) < 3) if (((sbuf_ptr + SBUF_SIZE - l) & SBUF_MASK) < 3)
+50 -14
View File
@@ -50,6 +50,7 @@ __xdata char port_names[9][PORT_NAME_SIZE];
extern __xdata uint16_t management_vlan; extern __xdata uint16_t management_vlan;
extern __xdata uint8_t sfp_speed[2]; extern __xdata uint8_t sfp_speed[2];
extern __xdata uint8_t sfp_pins_last; extern __xdata uint8_t sfp_pins_last;
extern __xdata uint8_t sfp_options[2];
__xdata uint8_t gpio_last_value[8] = { 0 }; __xdata uint8_t gpio_last_value[8] = { 0 };
// Temporatly for str to hex convertion value. // Temporatly for str to hex convertion value.
@@ -190,8 +191,11 @@ uint8_t atoi_byte(__xdata uint8_t *out, uint8_t idx)
uint8_t num = 0; uint8_t num = 0;
while (isnumber(cmd_buffer[idx])) { while (isnumber(cmd_buffer[idx])) {
uint8_t val = cmd_buffer[idx] - '0';
err = 0; err = 0;
num = (num * 10) + cmd_buffer[idx] - '0'; if (num > 25 || (num == 25 && val > 5))
return 1;
num = (num * 10) + val;
idx++; idx++;
} }
@@ -208,6 +212,8 @@ uint8_t atoi_short(__xdata uint16_t *vlan, uint8_t idx)
while (isnumber(cmd_buffer[idx])) { while (isnumber(cmd_buffer[idx])) {
err = 0; err = 0;
uint8_t val = cmd_buffer[idx] - '0'; uint8_t val = cmd_buffer[idx] - '0';
if (*vlan > 6553 || (*vlan == 6553 && val > 5))
return 1;
*vlan = (*vlan * 10) + val; *vlan = (*vlan * 10) + val;
idx++; idx++;
} }
@@ -341,6 +347,8 @@ void parse_vlan(void)
return; return;
} }
if (cmd_compare(2, "mgmt")) { if (cmd_compare(2, "mgmt")) {
if (vlan_settings.vlan > 4094)
goto err;
management_vlan = vlan_settings.vlan; management_vlan = vlan_settings.vlan;
if (!vlan_settings.vlan) if (!vlan_settings.vlan)
print_string("Management VLAN disabled\n"); print_string("Management VLAN disabled\n");
@@ -348,6 +356,8 @@ void parse_vlan(void)
print_string("Management VLAN set to "); print_short(management_vlan); write_char('\n'); print_string("Management VLAN set to "); print_short(management_vlan); write_char('\n');
return; return;
} }
if (!vlan_settings.vlan || vlan_settings.vlan > 4094)
goto err;
uint8_t w = 2; uint8_t w = 2;
if (cmd_words_len > w && isletter(cmd_buffer[cmd_words_b[w]])) { if (cmd_words_len > w && isletter(cmd_buffer[cmd_words_b[w]])) {
register uint8_t i = 0; register uint8_t i = 0;
@@ -720,17 +730,18 @@ void parse_mtu(void)
print_string("Port "); print_byte(machine.log_to_phys_port[p]); print_string("Port "); print_byte(machine.log_to_phys_port[p]);
write_char(' '); print_short(mtu); write_char('\n'); write_char(' '); print_short(mtu); write_char('\n');
} }
return;
} }
p = cmd_buffer[cmd_words_b[1]] - '1'; if (cmd_words_len != 3 || cmd_buffer[cmd_words_b[1]] < '1'
p = machine.phys_to_log_port[p]; || cmd_buffer[cmd_words_b[1]] > '9'
print_byte(p); || cmd_buffer[cmd_words_b[1] + 1] > ' ') {
if (cmd_words_len != 3) {
print_string("mtu [port] [size]\n"); print_string("mtu [port] [size]\n");
return; return;
} }
atoi_short(&mtu, cmd_words_b[2]); p = machine.phys_to_log_port[cmd_buffer[cmd_words_b[1]] - '1'];
if (mtu > 0x3fff) { print_byte(p);
print_string("Maximum MTU is 16383\n"); if (atoi_short(&mtu, cmd_words_b[2]) || mtu < 64 || mtu > 0x3fff) {
print_string("MTU must be 64..16383\n");
return; return;
} }
REG_WRITE(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8), (mtu >> 10) & 0xf, (mtu >> 2) & 0xff, REG_WRITE(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8), (mtu >> 10) & 0xf, (mtu >> 2) & 0xff,
@@ -741,7 +752,7 @@ void parse_mtu(void)
void sfp_print_measurements(uint8_t sfp) void sfp_print_measurements(uint8_t sfp)
{ {
print_string("Options: "); print_byte(sfp_read_reg(sfp, 92)); write_char('\n'); print_string("Options: "); print_byte(sfp_read_reg(sfp, 92)); write_char('\n');
if (!(sfp_read_reg(sfp, 92) & 0x40)) if (!(sfp_options[sfp] & 0x40))
return; return;
print_string("Temp: "); print_byte(sfp_read_reg(sfp, 224)); print_byte(sfp_read_reg(sfp, 225)); write_char('\n'); print_string("Temp: "); print_byte(sfp_read_reg(sfp, 224)); print_byte(sfp_read_reg(sfp, 225)); write_char('\n');
print_string("Vcc: "); print_byte(sfp_read_reg(sfp, 226)); print_byte(sfp_read_reg(sfp, 227)); write_char('\n'); print_string("Vcc: "); print_byte(sfp_read_reg(sfp, 226)); print_byte(sfp_read_reg(sfp, 227)); write_char('\n');
@@ -1523,6 +1534,29 @@ void cmd_parser(void) __banked
igmp_show(); igmp_show();
else else
igmp_setup(); // Reverts to default with IP-MC being flooded igmp_setup(); // Reverts to default with IP-MC being flooded
} 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
* spaces would tokenize into several words - reject it instead
* of silently keeping the first one. */
if (cmd_words_len == 1) {
print_string_x(hostname);
write_char('\n');
} else if (cmd_words_len == 2) {
__xdata uint8_t *hp = &cmd_buffer[cmd_words_b[1]];
__xdata char *dst = hostname;
for (uint8_t hn = 0; hn < sizeof(hostname) - 1; hn++) {
uint8_t c = *hp++;
if (c == '\0' || c == '\r' || c == '\n')
break;
if (c < 0x20 || c > 0x7e || c == '"' || c == '\\')
c = '.';
*dst++ = c;
}
*dst = '\0';
} else {
print_string("Error: hostname [name] - the name must not contain spaces\n");
}
} else if (cmd_compare(0, "stp")) { } else if (cmd_compare(0, "stp")) {
if (cmd_compare(1, "on")) { if (cmd_compare(1, "on")) {
print_string("STP enabled\n"); print_string("STP enabled\n");
@@ -1535,11 +1569,13 @@ void cmd_parser(void) __banked
} }
} else if (cmd_compare(0, "pvid") && cmd_words_len == 3) { } else if (cmd_compare(0, "pvid") && cmd_words_len == 3) {
__xdata uint16_t pvid; __xdata uint16_t pvid;
uint8_t port; if (cmd_buffer[cmd_words_b[1]] >= '1'
port = cmd_buffer[cmd_words_b[1]] - '1'; && cmd_buffer[cmd_words_b[1]] <= '9'
port = machine.phys_to_log_port[port]; && cmd_buffer[cmd_words_b[1] + 1] <= ' '
if (!atoi_short(&pvid, cmd_words_b[2])) && !atoi_short(&pvid, cmd_words_b[2]) && pvid && pvid <= 4094)
port_pvid_set(port, pvid); port_pvid_set(machine.phys_to_log_port[cmd_buffer[cmd_words_b[1]] - '1'], pvid);
else
print_string("Error: pvid <port> <1-4094>\n");
} else if (cmd_compare(0, "vlan")) { } else if (cmd_compare(0, "vlan")) {
parse_vlan(); parse_vlan();
} else if (cmd_compare(0, "isolate")) { } else if (cmd_compare(0, "isolate")) {
+17
View File
@@ -41,6 +41,7 @@ __xdata uip_ipaddr_t server;
#define DHCP_REBIND_LEN 4 #define DHCP_REBIND_LEN 4
#define DHCP_CLIENT_ID 61 #define DHCP_CLIENT_ID 61
#define DHCP_CLIENT_ID_LEN 7 #define DHCP_CLIENT_ID_LEN 7
#define DHCP_HOSTNAME 12
#define DHCP_REQUEST_IP 50 #define DHCP_REQUEST_IP 50
#define DHCP_REQUEST_IP_LEN 4 #define DHCP_REQUEST_IP_LEN 4
#define DHCP_PARAMS 55 #define DHCP_PARAMS 55
@@ -116,6 +117,20 @@ void dhcp_addopt_client_id(void)
} }
void dhcp_addopt_hostname(void)
{
uint8_t len = 0;
while (hostname[len])
len++;
if (!len)
return;
DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_HOSTNAME;
DHCP_OPT[dhcp_state.opt_ptr++] = len;
memcpy(&DHCP_OPT[dhcp_state.opt_ptr], hostname, len);
dhcp_state.opt_ptr += len;
}
void dhcp_addopt_request_ip(void) void dhcp_addopt_request_ip(void)
{ {
DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_REQUEST_IP; DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_REQUEST_IP;
@@ -152,6 +167,7 @@ void dhcp_send_discover(void)
dhcp_addopt_client_id(); dhcp_addopt_client_id();
dhcp_addopt_request_ip(); dhcp_addopt_request_ip();
dhcp_addopt_hostname();
DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_PARAMS; DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_PARAMS;
DHCP_OPT[dhcp_state.opt_ptr++] = 3; DHCP_OPT[dhcp_state.opt_ptr++] = 3;
@@ -188,6 +204,7 @@ void dhcp_send_request(void)
dhcp_addopt_client_id(); dhcp_addopt_client_id();
dhcp_addopt_request_ip(); dhcp_addopt_request_ip();
dhcp_addopt_server_id(); dhcp_addopt_server_id();
dhcp_addopt_hostname();
DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_PARAMS; DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_PARAMS;
DHCP_OPT[dhcp_state.opt_ptr++] = 3; DHCP_OPT[dhcp_state.opt_ptr++] = 3;
+55
View File
@@ -0,0 +1,55 @@
# 2G040210GSM
The following is a documentation for the managed switch marked as `2G040210GSM`
and sold by Mokerlink.
### Label specifications
- **Name**: 4-port 2.5G Web Managed Switch
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1000 / 2500 / 10000 Mbps
- **Power**: 12V DC, 1A barrel connector
### What works
The device is fully supported:
- All 4 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ port supports 1G, 2.5G and 10G modules
- LEDs work with the same indiciations as the OEM firmware (use KP_9000_6XHML_X2 in machine.h if building yourself or the corresponding pre-compiled binary)
- untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs.
### Hardware overview
Front
<img src="photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-front.jpeg" width="300" />
Label
<img src="photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-label.jpeg" width="300" />
### PCB overview
**Board markings**
- Top silkscreen: 2M-PCB43-V1.1
Top side
<img src="photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-top.jpeg" width="300" />
Bottom
<img src="photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-bottom.jpeg" width="300" />
### J1, serial console
| `J8` pin | Signal |
| -------- | ----------- |
| 1 | RX (Input) |
| 2 | TX (Output) |
| 3 | GND |
| 4 | 3V3 |
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+39
View File
@@ -0,0 +1,39 @@
# FG-4GT-2SX_V2.0
Following is documentation for unmanaged switch marked as `FG-4GT-2SX_V2.0`.
Original software is running UART on 9600 baud rate.
## Brands
* Ruiying RY-4GT-2SX
<img src="photos/FG-4GT-2SX_V2.0/RY-4GT-2SX_label.jpg" width="300" />
## What works
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- Both SFP ports supporting 1G, 2.5G and 10G modules
- LEDs
## PCB overview
**Board markings**
- Top silkscreen: FG-4GT-2SX_V2.0
Front panel
<img src="photos/FG-4GT-2SX_V2.0/chassis-front.jpg" width="300" />
Top side
<img src="photos/FG-4GT-2SX_V2.0/PCB-top.jpg" width="300" />
Bottom
<img src="photos/FG-4GT-2SX_V2.0/PCB-bottom.jpg" width="300" />
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+6
View File
@@ -1,4 +1,10 @@
# SWTG018AS-A V2.0 # 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 |
The following is a documentation for the unmanaged switch marked as `SWTG018AS-A V2.0`. The following is a documentation for the unmanaged switch marked as `SWTG018AS-A V2.0`.
It is e.g. sold under the Ampcom brand, but no branh-markings are found on the device. It is e.g. sold under the Ampcom brand, but no branh-markings are found on the device.
+43
View File
@@ -0,0 +1,43 @@
### SWTG024AS-A-V2.0.1_4C_2SFP
It is highly similar to SWTG024AS-V2.0, with the only difference being the GPIO configuration for the SFP port.
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| Horaco | ZX-SG4T2 | | PCB-SWTG024AS-A-V2.0.1_19650 | P25D40SH | ??? |
### Label specifications
- **Name**:
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1000 / 2500 / 10000 Mbps
<img src="photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-label.jpg" width="300" />
### What works
The device is fully supported:
- ALL 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ port supports 1G, 2.5G and 10G modules
### PCB overview
**Board markings**
- Top silkscreen: PCB-SWTG024AS-A-V2.0.1_19650
Top side
<img src="photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-top.jpg" width="300" />
Bottom
<img src="photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-bottom.jpg" width="300" />
### J1, serial console
| `J1` pin | Signal |
| -------- | ----------- |
| 1 | 3V3 |
| 2 | GND |
| 3 | RX (Input) |
| 4 | TX (Output) |
+44
View File
@@ -0,0 +1,44 @@
### SWTG024AS-A-V2.0.1_5C_1SFP
It is highly similar to SWTG024AS-V2.0, with the only difference being the GPIO configuration for the SFP port.
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| Horaco | HC-SWTGW215AS | | PCB-SWTG024AS-A-V2.0.1_19650 | W25Q16JV | 8272N |
### Label specifications
- **Name**:
- **Ports**:
- 5 × RJ45: 10/100/1000/2500 Mbps
- 1 × SFP+: 1000 / 2500 / 10000 Mbps
<!-- <img src="" width="300" /> -->
### What works
The device is fully supported:
- ALL 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ port supports 1G, 2.5G and 10G modules
- LEDs work with the same indiciations as the OEM firmware
### PCB overview
**Board markings**
- Top silkscreen: PCB-SWTG024AS-A-V2.0.1_19650
Top side
<!-- <img src="" width="300" /> -->
Bottom
<!-- <img src="" width="300" /> -->
### J1, serial console
| `J1` pin | Signal |
| -------- | ----------- |
| 1 | 3V3 |
| 2 | GND |
| 3 | RX (Input) |
| 4 | TX (Output) |
+49
View File
@@ -0,0 +1,49 @@
### SWTG024AS-V2.0
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| hongyavision | LG-SG5T1 | No | PCB-SWTG024AS-V2.0_16895 | 25Q40 | 8272 |
### Label specifications
- **Name**:
- **Ports**:
- 5 × RJ45: 10/100/1000/2500 Mbps
- 1 × SFP+: 1000 / 2500 / 10000 Mbps
- **Power**: 12V DC, 1A 5525 connector
<img src="photos/SWTG024AS-V2.0/label.jpg" width="300" />
### What works
The device is fully supported:
- ALL 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps
- The SFP+ port supports 1G, 2.5G and 10G modules
- LEDs work with the same indiciations as the OEM firmware
- Online update does not work with 512KiB flash.
### PCB overview
**Board markings**
- Top silkscreen: PCB-SWTG024AS-V2.0
Top side
<img src="photos/SWTG024AS-V2.0/pcb_top.jpg" width="300" />
Bottom
<img src="photos/SWTG024AS-V2.0/pcb_bottom.jpg" width="300" />
### J1, serial console
| `J1` pin | Signal |
| -------- | ----------- |
| 1 | GND |
| 2 | RX (Input) |
| 3 | TX (Output) |
Note,`R52``R53`may not be installed.You need to bridge them using either solder or resistors.
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+2 -2
View File
@@ -9,8 +9,8 @@ Also the RJ45 connectors can be all plastic/non-shielded or with metal shielding
|Brand|Type|Managed|PCB|PCB Label|Flash|Chip RTL| |Brand|Type|Managed|PCB|PCB Label|Flash|Chip RTL|
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| LIANGUO |SWTG024AS |No| SWTG024AS-v2.0-17452 | CM-23-11-2336 023-17453| 512 KiB | 8272 | | LIANGUO |SWTG024AS |No| SWTG024AS-v2.0-17452 | CM-23-11-2336 023-17453| 512 KiB | 8272 |
| Haraco |ZX-SWTG124AS | Yes | SWTG024AS-v2.0 | ??? | ??? | 8272 | | Horaco |ZX-SWTG124AS | Yes | SWTG024AS-v2.0 | ??? | ??? | 8272 |
| Xikestore |SKS3200M-4GPY2XF | Yes | SWTG024AS-v1.0 | CM-23-08-2043 023-16721 | ??? | 8272 | | Xikestore |SKS3200M-4GPY2XF | Yes | SWTG024AS-v1.0 | CM-23-08-2043 023-16721 | 2048 KiB | 8272 |
| Sodola | SL-SWTG124AS-D | Yes | SWTG024AS-v2.0-17452 | ??? | 2048 KiB | 8272 | | Sodola | SL-SWTG124AS-D | Yes | SWTG024AS-v2.0-17452 | ??? | 2048 KiB | 8272 |
## PCB ## PCB
+1
View File
@@ -6,6 +6,7 @@
| Mokerlink | ZX-SWTGW218AS | Yes| SWTG118AS-V2.0-16029 | 2MB (FM25Q16A)| 8273N + 8224N | | Mokerlink | ZX-SWTGW218AS | Yes| SWTG118AS-V2.0-16029 | 2MB (FM25Q16A)| 8273N + 8224N |
| Sodola | | | | | | | Sodola | | | | | |
| Horaco | | | | | | | Horaco | | | | | |
| XikeStor | SKS3200-8E1X | Yes | SWTG118AS-V2.1-17462 | 2MB (25Q16JVSIQ) | |
## Photos ## Photos
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 MiB

+2 -1
View File
@@ -37,12 +37,13 @@ This list is incomplete.
| Brand | Partnumber | | Brand | Partnumber |
| ---------- |----------- | | ---------- |----------- |
| GigaDevice | GD25Q32E | | GigaDevice | GD25Q32E |
| Fundan | FM25Q16A |
| Puya | P25D40SH |
| Winbond | W25Q16JV | | Winbond | W25Q16JV |
| Winbond | W25Q32FV | | Winbond | W25Q32FV |
| Winbond | W25Q32JV | | Winbond | W25Q32JV |
| Winbond | W25Q16JL | | Winbond | W25Q16JL |
| Winbond | W25Q16DV | | Winbond | W25Q16DV |
| Winbond | W25Q80DV | | Winbond | W25Q80DV |
| Fundan | FM25Q16A |
*NOTE*: Part numbers are incomplete. Part numbers may contain additional information such as package, temperature specifications, and even the number of devices on a reel. So always check the datasheet so that you have the right orderable partnumber. *NOTE*: Part numbers are incomplete. Part numbers may contain additional information such as package, temperature specifications, and even the number of devices on a reel. So always check the datasheet so that you have the right orderable partnumber.
+135
View File
@@ -0,0 +1,135 @@
# 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.
## Architecture
All translation logic lives in `html/i18n.js`. The file contains:
- 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
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.
## How to Add a New Language
### 1. Add a dictionary entry in `html/i18n.js`
Append a new sub-object to the `LANG` object. Every key from `LANG.en` must 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
},
};
```
### 2. Add the language to the navigation sidebar
In `html/navigation.js`, add an `<option>` to the language selector:
```js
+ "<option value='en'>English</option><option value='ja'>日本語</option>"
```
Replace with:
```js
+ "<option value='en'>English</option><option value='ja'>日本語</option><option value='LANGCODE'>Native Name</option>"
```
### 3. 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.
## 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:
```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>
```
On page load, `applyTranslation()` sets:
- `el.value` for `<input type="submit|button">`
- `el.textContent` for `<option>`, `<title>`
- `el.innerHTML` for everything else
### (B) `t('key')` call (imperative — for JavaScript strings)
When generating HTML or text in JavaScript, wrap translatable strings with `t()`:
```js
td.appendChild(document.createTextNode(t('common_port') + i));
td.innerHTML = t('port_auto');
iHTML += "<tr><td>" + t('port_vendor') + "</td></tr>";
```
### 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.
## 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 (~34 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"`)
## 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>`).
+29 -12
View File
@@ -1,18 +1,35 @@
# Supported Hardware # Supported Hardware
The following devices have been tested and are fully working: The following devices have been tested and are fully working:
- Horaco ZX_SG4T2
- keepLINK kp-9000-6hx-x2 (RTL8372: 4x 2.5GBit + 2x 10GBit SFP+)
- keepLINK KP-9000-6XHML-X2, same as above, but Managed
- keepLINK kp-9000-6hx-x (RTL8372 + RTL8221B 2.5GBit PHY: 5 x 2.5GBit + 1x 10GBit SFP+)
- keepLINK kp-9000-9xh-x-eu (1 x RTL8373 + RTL8224: 8x 2.5GBit + 1x 10GBit SFP+)
- Lianguo LG-SWTGW218AS (RTL8373 + RTL8224 PHY: 8x 2.5GBit + 1x 10GBit SFP+)
- No-Name ZX-SWTGW215AS, managed version of kp-9000-6hx-x, ordered on
AliExpress as keepLINK 5+1 port managed
- TrendNet TEG-S562 (RTL8372: 4x 2.5GBit + 2x 10GBit SFP+)
- FNS-1200P (RTL8372: 4x 2.5GBit + 2x 10GBit SFP+)
Other device based on RTL8272/3 that may work are described here: [Up-N-Atoms 2.5 GBit RTL Switch hacking guide] | Brand | Type | Managed | PCB | Flash | Ports |
(https://github.com/up-n-atom/SWTG118AS) |----------|-----------------|---------|---------------------------------------------------------------------------|-------|-------|
| 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 |
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Hisource | Hi-K0801WS | No | [PCB-KO801W-V2.0](devices/HI-K0801WS.md) | | 8 + 1 |
| hongyavision | LG-SG5T1 | No | [PCB-SWTG024AS-V2.0_16895](devices/SWTG024AS-V2.0.md) | 0.5M | 5 + 1 |
| Horaco | HC-SWTGW215AS | Yes | [SWTG024AS-A-V2.0.1_19650 5C 1SFP](devices/SWTG024AS-A-V2.0.1_5C_1SFP.md) | ? | 5 + 1 |
| Horaco | HC-SWTGW218AS | Yes | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | | 8 + 1 |
| Horaco | ZX310S-4T2XH | Yes | [PCB-SL310S-4T1T1X-V1.0.1-24107](devices/ZX310S-4T2XH.md) | 2M | 5 + 1 |
| Horaco | ZX310S-4T2XT | Yes | [PCB-SL310S-4T2XT-V1.0.0-22273](devices/ZX310S-4T2XT.md) | 2M | 6 |
| Horaco | ZX-SG4T2 | No | [SWTG024AS-A-V2.0.1_19650_4C_2SFP](devices/SWTG024AS-A-V2.0.1_4C_2SFP.md) | 0.5M | 4 + 2 |
| Horaco | ZX-SWTG124AS | Yes | [SWTG024AS-v2.0](devices/SWTG024AS.md) | | 4 + 2 |
| Keeplink | KP-9000-6XH-X2 | No | [2M-PCB43-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 |
| LIANGUO | SWTG024AS | No | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 0.5M | 4 + 2 |
| Lianguo | ZX-SWTGW215AS | Yes | [PCB-SWTG115AS-V2.0](devices/SWTGW215AS.md) | 2M | 5 + 1 |
| Mokerlink| ZX-SWTGW218AS | Yes | [SWTG118AS-V2.0-16029](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| Ruiying | RY-4GT-2SX | No | [FG-4GT-2SX_V2.0](devices/FG-4GT-2SX_V2.0.md) | 4M | 4 + 2 |
| Sodola | SL-SWTG124AS-D | Yes | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 2M | 4 + 2 |
| Steamemo | IG204-V1 | No | [PB-2131](devices/STEAMEMO_IG204_V1.md) | | 4 + 2 |
| TrendNet | TEG-S562 | No | [TEG-S563/EU H/W: V1.0R](devices/TEG-S562.md) | 2M | 4 + 2 |
| 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 |
Other device based on RTL8272/3 that may work are described here: [Up-N-Atoms 2.5 GBit RTL Switch hacking guide](https://github.com/up-n-atom/SWTG118AS)
Many of the RTL8272/3 devices come in versions with PoE support. The RTLPlayground usually also Many of the RTL8272/3 devices come in versions with PoE support. The RTLPlayground usually also
works on these, however, no support for configuring PoE is provided, simply because these works on these, however, no support for configuring PoE is provided, simply because these
+5 -4
View File
@@ -1,17 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<script src="/main.js"></script> <script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>Ingress and Egress Bandwidth</title> <title data-i18n="bw_title">Ingress and Egress Bandwidth</title>
</head> </head>
<body> <body>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div> <div id="ports"></div>
<h1>Ingress and Egress Bandwidth</h1> <h1 data-i18n="bw_heading">Ingress and Egress Bandwidth</h1>
<table id="bwtable"> <table id="bwtable">
<tr> <th> </th> <th colspan="3"> Ingress </th> <th colspan="2">Egress</th> <th></th></tr> <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>Port</th> <th>Limit</th> <th>Bandwidth [kBit/s]</th> <th>Flow Control</th> <th>Limit</th> <th>Bandwidth [kBit/s]</th> <th>Apply</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> </table>
<script src="/bandwidth.js"></script> <script src="/bandwidth.js"></script>
</div> </div>
+8 -8
View File
@@ -6,18 +6,18 @@ function createBW() {
console.log("CREATING TABLE ", tbl.rows.length); console.log("CREATING TABLE ", tbl.rows.length);
for (let i = 2; i < 2 + numPorts; i++) { for (let i = 2; i < 2 + numPorts; i++) {
const tr = tbl.insertRow(); const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(`Port ${i-1}`)); let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i-1)));
td = tr.insertCell(); td = tr.insertCell();
td.innerHTML = limit.replaceAll("limit_port", "ilimit_port_" + i).replace("exec()", "iClicked(" + i + ")"); td.innerHTML = limit.replaceAll("limit_port", "ilimit_port_" + i).replace("exec()", "iClicked(" + i + ")");
td = tr.insertCell(); td = tr.insertCell();
td.innerHTML = 'UNLIMITED'; td.innerHTML = t('bw_unlimited');
td = tr.insertCell(); td = tr.insertCell();
td.innerHTML = limit.replaceAll("limit_port", "fc_port_" + i).replace("exec()", "document.getElementById('bwapply_" + i + "').disabled=false;"); td.innerHTML = limit.replaceAll("limit_port", "fc_port_" + i).replace("exec()", "document.getElementById('bwapply_" + i + "').disabled=false;");
td = tr.insertCell(); td = tr.insertCell();
td.innerHTML = limit.replaceAll("limit_port", "elimit_port_" + i).replace("exec()", "eClicked(" + i + ")"); td.innerHTML = limit.replaceAll("limit_port", "elimit_port_" + i).replace("exec()", "eClicked(" + i + ")");
td = tr.insertCell(); td = tr.insertCell();
td.innerHTML = 'UNLIMITED'; td.innerHTML = t('bw_unlimited');
var button = '<button type="button" id="bwapply_' + i + '" style="margin: 0 0 0 24px" onclick="applyBandwidth(' + i + ');">Apply</button>'; 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 = tr.insertCell();
td.innerHTML = button; td.innerHTML = button;
document.getElementById("bwapply_" + i).disabled = true; document.getElementById("bwapply_" + i).disabled = true;
@@ -31,7 +31,7 @@ function iClicked(i)
var tbl = document.getElementById('bwtable'); var tbl = document.getElementById('bwtable');
var tr = tbl.rows[i]; var tr = tbl.rows[i];
if (!document.getElementById("ilimit_port_" + i).checked) { if (!document.getElementById("ilimit_port_" + i).checked) {
tr.cells[2].innerHTML = "UNLIMITED"; tr.cells[2].innerHTML = t('bw_unlimited');
document.getElementById("fc_port_" + i).disabled = true; document.getElementById("fc_port_" + i).disabled = true;
document.getElementById("fc_port_" + i).checked = true; document.getElementById("fc_port_" + i).checked = true;
} else { } else {
@@ -47,7 +47,7 @@ function eClicked(i)
var tbl = document.getElementById('bwtable'); var tbl = document.getElementById('bwtable');
var tr = tbl.rows[i]; var tr = tbl.rows[i];
if (!document.getElementById("elimit_port_" + i).checked) { if (!document.getElementById("elimit_port_" + i).checked) {
tr.cells[5].innerHTML = "UNLIMITED"; tr.cells[5].innerHTML = t('bw_unlimited');
} else { } else {
tr.cells[5].innerHTML = '<input id="ebw_' + i + iLayout + i + ')" value="0"/>'; tr.cells[5].innerHTML = '<input id="ebw_' + i + iLayout + i + ')" value="0"/>';
} }
@@ -110,12 +110,12 @@ function getBW() {
document.getElementById("ilimit_port_" + (n+1)).checked = p.iLimited; document.getElementById("ilimit_port_" + (n+1)).checked = p.iLimited;
document.getElementById("elimit_port_" + (n+1)).checked = p.eLimited; document.getElementById("elimit_port_" + (n+1)).checked = p.eLimited;
if (!p.iLimited) { if (!p.iLimited) {
tr.cells[2].innerHTML = "UNLIMITED"; tr.cells[2].innerHTML = t('bw_unlimited');
} else { } else {
tr.cells[2].innerHTML = '<input id="ibw_' + (n+1) + iLayout + (n+1) + ')" value="' + iBW +'"/>'; tr.cells[2].innerHTML = '<input id="ibw_' + (n+1) + iLayout + (n+1) + ')" value="' + iBW +'"/>';
} }
if (!p.eLimited) { if (!p.eLimited) {
tr.cells[5].innerHTML = "UNLIMITED"; tr.cells[5].innerHTML = t('bw_unlimited');
} else { } else {
tr.cells[5].innerHTML = '<input id="ebw_' + (n+1) + iLayout + (n+1) + ')" value="' + eBW +'"/>'; tr.cells[5].innerHTML = '<input id="ebw_' + (n+1) + iLayout + (n+1) + ')" value="' + eBW +'"/>';
} }
+9 -3
View File
@@ -14,6 +14,7 @@ const conf_cmds = [
/^pvid\s+\d{1,2}\s+\d{1,4}$/, /^pvid\s+\d{1,2}\s+\d{1,4}$/,
/^ingress(\s+\d{1,2}[tua])+$/, /^ingress(\s+\d{1,2}[tua])+$/,
/^ingress\s+[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+$/, /^port\s+\d{1,2}\s+name\s+\S+$/,
/^eee(\s+\d{1,2})?\s+(on|off)$/, /^eee(\s+\d{1,2})?\s+(on|off)$/,
/^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/, /^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,
@@ -24,6 +25,7 @@ const conf_cmds = [
/^igmp\s+(on|off)$/, /^igmp\s+(on|off)$/,
/^mtu\s+\d{1,2}\s+\d+$/, /^mtu\s+\d{1,2}\s+\d+$/,
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/, /^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
/^hostname\s+.{1,23}$/,
]; ];
const conf_overwrite = [ const conf_overwrite = [
/^ip\b/, /^ip\b/,
@@ -36,6 +38,7 @@ const conf_overwrite = [
/^vlan\s+\d{1,4}(?!\s+mgmt\b)/, /^vlan\s+\d{1,4}(?!\s+mgmt\b)/,
/^pvid\s+\d{1,2}\b/, /^pvid\s+\d{1,2}\b/,
/^ingress\b/, /^ingress\b/,
/^port\s+\d{1,2}(?!\s+name\b)/,
/^port\s+\d{1,2}\s+name\b/, /^port\s+\d{1,2}\s+name\b/,
/^eee\s+\d{1,2}\b/, /^eee\s+\d{1,2}\b/,
/^eee\b/, /^eee\b/,
@@ -47,6 +50,7 @@ const conf_overwrite = [
/^igmp\b/, /^igmp\b/,
/^mtu\s+\d{1,2}\b/, /^mtu\s+\d{1,2}\b/,
/^bw\s+(in|out)\s+\d{1,2}\b/, /^bw\s+(in|out)\s+\d{1,2}\b/,
/^hostname\b/,
]; ];
function parseConf(s){ function parseConf(s){
@@ -57,8 +61,7 @@ function parseConf(s){
const deleteMatch = line.match(/^vlan\s+(\d{1,4})\s+d$/); const deleteMatch = line.match(/^vlan\s+(\d{1,4})\s+d$/);
if (deleteMatch) { if (deleteMatch) {
const prefix = "vlan " + deleteMatch[1] + " "; const prefix = "vlan " + deleteMatch[1] + " ";
configuration = configuration.filter(c => configuration = configuration.filter(c => !c.startsWith(prefix));
c !== "vlan " + deleteMatch[1] && !c.startsWith(prefix));
continue; continue;
} }
console.log(l + ' --> ' + line); console.log(l + ' --> ' + line);
@@ -71,10 +74,13 @@ function parseConf(s){
let m = line.match(x); let m = line.match(x);
let matchStr = m[0]; let matchStr = m[0];
configuration = configuration.filter(item => configuration = configuration.filter(item =>
!(item === matchStr || item.startsWith(matchStr + " "))); !(item === matchStr || (item.startsWith(matchStr + " ") && !item.endsWith(" mgmt") && !item.startsWith(matchStr + " name "))));
break; 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); configuration.push(line);
} }
console.log("Configuration now:"); console.log("Configuration now:");
+7 -6
View File
@@ -1,21 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<script src="/main.js"></script> <script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>EEE Configuration</title> <title data-i18n="eee_title">EEE Configuration</title>
</head> </head>
<body> <body>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div> <div id="ports"></div>
<h1>EEE Status</h1> <h1 data-i18n="eee_heading">EEE Status</h1>
<table id="eeetable"> <table id="eeetable">
<tr> <th> </th> <th colspan="3"> Advertising </th> <th colspan="3">Link-Partner advertises</th> <th></th></tr> <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>Port</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th>Active?</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> </table>
<div> <div>
<input style="width:20%;" class="action" id="eee_enable" onclick="eeeSub(0, 1);" type="button" value="Enable EEE"> <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_enable" onclick="eeeSub(0, 0);" type="button" value="Disable EEE"> <input style="width:20%;" class="action" id="eee_disable" onclick="eeeSub(0, 0);" type="button" data-i18n="eee_disable" value="Disable EEE">
</div> </div>
<script src="/eee.js"></script> <script src="/eee.js"></script>
<script src="/eee_sub.js"></script> <script src="/eee_sub.js"></script>
+3 -3
View File
@@ -5,7 +5,7 @@ function createEEE() {
for (let i = 2; i < 2 + numPorts; i++) { for (let i = 2; i < 2 + numPorts; i++) {
console.log("Table row: " + i + "pState: " + pState[i-2]); console.log("Table row: " + i + "pState: " + pState[i-2]);
const tr = tbl.insertRow(); const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(`Port ${i-1}`)); let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i-1)));
for (let j = 0; j < 7; j++) { for (let j = 0; j < 7; j++) {
td = tr.insertCell(); td.appendChild(document.createTextNode(" ")); td = tr.insertCell(); td.appendChild(document.createTextNode(" "));
} }
@@ -28,8 +28,8 @@ function getEEE() {
let tr = tbl.rows[n+1]; let tr = tbl.rows[n+1];
if (!p.isSFP) { if (!p.isSFP) {
let eee = parseInt(p.eee,2); let lp = parseInt(p.eee_lp,2); let eee = parseInt(p.eee,2); let lp = parseInt(p.eee_lp,2);
tr.cells[1].innerHTML = `${eee&4?"ON":"OFF"}`; tr.cells[2].innerHTML = `${eee&2?"ON":"OFF"}`; tr.cells[3].innerHTML = `${eee&1?"ON":"OFF"}`; 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?"ON":"OFF"}`; tr.cells[5].innerHTML = `${lp&2?"ON":"OFF"}`; tr.cells[6].innerHTML = `${lp&1?"ON":"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.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('disabled', pState[i-2] < 0); tr.classList.toggle('isNOK', !p.active); tr.classList.toggle('isOK', p.active);
} }
+603
View File
@@ -0,0 +1,603 @@
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_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_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_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);
});
});
+4 -3
View File
@@ -2,6 +2,7 @@
<html> <html>
<script src="/main.js"></script> <script src="/main.js"></script>
<script src="/main_info.js"></script> <script src="/main_info.js"></script>
<script src="/i18n.js"></script>
<script> <script>
window.addEventListener("load", function() { window.addEventListener("load", function() {
update( () => { update( () => {
@@ -10,17 +11,17 @@
}); });
</script> </script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>FreeSwitchOS Main Page</title> <title data-i18n="index_title">FreeSwitchOS Main Page</title>
</head> </head>
<body> <body>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div> <div id="ports"></div>
<h1>Switch Configuration</h1> <h1 data-i18n="index_heading">Switch Configuration</h1>
<table id="infoTable"> <table id="infoTable">
<tr> <tr>
<th colspan="2">Settings</th> <th colspan="2" data-i18n="index_settings">Settings</th>
</tr> </tr>
<tbody> <tbody>
</tbody> </tbody>
+14 -3
View File
@@ -1,16 +1,27 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<script src="/main.js"></script> <script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>FreeSwitchOS L2 Configuration</title> <title data-i18n="l2_title">FreeSwitchOS L2 Configuration</title>
</head> </head>
<body> <body>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div> <div id="ports"></div>
<h1>L2 Configuration</h1> <h1 data-i18n="l2_heading">L2 Configuration</h1>
<p><span data-i18n="l2_shown">Shown:</span> <span id="l2count">-</span></p>
<table id="l2table"> <table id="l2table">
<tr> <th>Port</th> <th>MAC</th> <th>VLAN</th> <th>Type</th> <th>Remove Entry</th></tr> <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> <script src="/l2.js"></script>
</table> </table>
</div> </div>
+68 -17
View File
@@ -9,22 +9,22 @@ function fillStats() {
if (tbl.rows.length > 1) { if (tbl.rows.length > 1) {
for (let i = 0; i < numPorts; i++) { for (let i = 0; i < numPorts; i++) {
console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] +1]); console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] +1]);
tbl.rows[i+1].cells[1].innerHTML = `${linkS[pState[i]+1]}`; tbl.rows[i+1].cells[1].innerHTML = linkText(pState[i]+1);
tbl.rows[i+1].cells[2].innerHTML = `${txG[i]} pkts`; tbl.rows[i+1].cells[2].innerHTML = `${txG[i]}` + t('common_pkts');
tbl.rows[i+1].cells[3].innerHTML = `${txB[i]} pkts`; tbl.rows[i+1].cells[3].innerHTML = `${txB[i]}` + t('common_pkts');
tbl.rows[i+1].cells[4].innerHTML = `${rxG[i]} pkts`; tbl.rows[i+1].cells[4].innerHTML = `${rxG[i]}` + t('common_pkts');
tbl.rows[i+1].cells[5].innerHTML = `${rxB[i]} pkts`; tbl.rows[i+1].cells[5].innerHTML = `${rxB[i]}` + t('common_pkts');
} }
} else { } else {
for (let i = 0; i < numPorts; i++) { for (let i = 0; i < numPorts; i++) {
console.log("Table row: " + i); console.log("Table row: " + i);
const tr = tbl.insertRow(); const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(`Port ${i+1}`)); let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i+1)));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${linkS[pState[i]+1]}`)); td = tr.insertCell(); td.appendChild(document.createTextNode(linkText(pState[i]+1)));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]} pkts`)); td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]}` + t('common_pkts')));
td = tr.insertCell();td.appendChild(document.createTextNode(`${txB[i]} pkts`)); td = tr.insertCell();td.appendChild(document.createTextNode(`${txB[i]}` + t('common_pkts')));
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxG[i]} pkts`)); td = tr.insertCell();td.appendChild(document.createTextNode(`${rxG[i]}` + t('common_pkts')));
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxB[i]} pkts`)); td = tr.insertCell();td.appendChild(document.createTextNode(`${rxB[i]}` + t('common_pkts')));
} }
} }
} }
@@ -64,6 +64,51 @@ function delL2(idx) {
xhttp.timeout = 1500; xhttp.send(); 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') return e.port === 'CPU' ? Number.MAX_SAFE_INTEGER : 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) function fillL2(s)
{ {
var tbl = document.getElementById('l2table'); var tbl = document.getElementById('l2table');
@@ -71,7 +116,13 @@ function fillL2(s)
return; return;
s.sort(l2CMP); s.sort(l2CMP);
s = uniq(s); s = uniq(s);
var s = s.map(function(e) { e.port = e.port != 9 ? e.port : "CPU"; return e; }); l2All = s;
renderL2();
l2Entries = [];
}
function paintL2(tbl, s)
{
console.log("L2: ", JSON.stringify(s)); console.log("L2: ", JSON.stringify(s));
for (let i = 0; i < s.length; i++) { for (let i = 0; i < s.length; i++) {
var e = s[i]; var e = s[i];
@@ -80,19 +131,19 @@ function fillL2(s)
tbl.rows[i+1].cells[0].innerHTML = `${e.port}`; tbl.rows[i+1].cells[0].innerHTML = `${e.port}`;
tbl.rows[i+1].cells[1].innerHTML = `${e.mac}`; tbl.rows[i+1].cells[1].innerHTML = `${e.mac}`;
tbl.rows[i+1].cells[2].innerHTML = `${e.vlan}`; tbl.rows[i+1].cells[2].innerHTML = `${e.vlan}`;
tbl.rows[i+1].cells[4].innerHTML = '<button type="button" onclick="delL2(' + e.idx + ');">Delete</button>'; tbl.rows[i+1].cells[3].innerHTML = `${e.type}`;
tbl.rows[i+1].cells[4].innerHTML = '<button type="button" onclick="delL2(' + e.idx + ');">' + t('l2_delete') + '</button>';
} else { } else {
const tr = tbl.insertRow(); const tr = tbl.insertRow();
let td = tr.insertCell(); td.innerHTML = `${e.port}`; let td = tr.insertCell(); td.innerHTML = `${e.port}`;
td = tr.insertCell(); td.innerHTML = `${e.mac}`; td = tr.insertCell(); td.innerHTML = `${e.mac}`;
td = tr.insertCell(); td.innerHTML = `${e.vlan}`; td = tr.insertCell(); td.innerHTML = `${e.vlan}`;
td = tr.insertCell(); td.innerHTML = `${e.type}`; td = tr.insertCell(); td.innerHTML = `${e.type}`;
td = tr.insertCell(); td.innerHTML = '<button type="button" onclick="delL2(' + e.idx + ');">Delete</button>'; td = tr.insertCell(); td.innerHTML = '<button type="button" onclick="delL2(' + e.idx + ');">' + t('l2_delete') + '</button>';
} }
} }
for (let i = tbl.rows.length - 1; i > s.length; i--) for (let i = tbl.rows.length - 1; i > s.length; i--)
tbl.deleteRow(i); tbl.deleteRow(i);
l2Entries = [];
} }
function getL2() { function getL2() {
@@ -103,8 +154,8 @@ function getL2() {
var s = s.map(function(e) { var s = s.map(function(e) {
e.vlan = parseInt(e.vlan, 16); e.vlan = parseInt(e.vlan, 16);
e.idx = parseInt(e.idx, 16); e.idx = parseInt(e.idx, 16);
e.type = e.type == "s" ? "static" : "learned"; e.type = e.type == "s" ? t('l2_static') : t('l2_learned');
e.port = e.port == 9 ? 9 : logToPhysPort[e.port]; e.port = e.port == 9 ? 'CPU' : logToPhysPort[e.port];
return e; return e;
}); });
l2Entries.push(...s); l2Entries.push(...s);
+7 -6
View File
@@ -1,24 +1,25 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<script src="/main.js"></script> <script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>Link Aggregation Configuration</title> <title data-i18n="lag_title">Link Aggregation Configuration</title>
</head> </head>
<body> <body>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div> <div id="ports"></div>
<h1>Link Aggregation Groups Configuration</h1> <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" value="Update / Create"></h2> <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> <div id="mLAG0"></div>
<br /> <br />
<h2>LAG 2 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub1" onclick="lagSub(1);" type="button" value="Update / Create"></h2> <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> <div id="mLAG1"></div>
<br /> <br />
<h2>LAG 3 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub2" onclick="lagSub(2);" type="button" value="Update / Create"></h2> <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> <div id="mLAG2"></div>
<br /> <br />
<h2>LAG 4 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub3" onclick="lagSub(3);" type="button" value="Update / Create"></h2> <h2>LAG 4 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub3" onclick="lagSub(3);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
<div id="mLAG3"></div> <div id="mLAG3"></div>
<script src="/lag.js"></script> <script src="/lag.js"></script>
</div> </div>
+7 -7
View File
@@ -1,30 +1,30 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<title>RTL Switch Login</title> <title data-i18n="login_title">RTL Switch Login</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="/i18n.js"></script>
<script> <script>
function removeNote() { function removeNote() {
document.getElementById("incorrect").innerHTML = ""; document.getElementById("incorrect").innerHTML = "";
} }
window.addEventListener("load", function() { window.addEventListener("load", function() {
if (document.referrer.endsWith("login.html")) if (document.referrer.endsWith("login.html"))
document.getElementById("incorrect").innerHTML = "Wrong password!"; document.getElementById("incorrect").innerHTML = t('login_wrong');
}); });
</script> </script>
</head> </head>
<body class="login_page"> <body class="login_page">
<div class = "center"> <div class = "center">
<h1> RTL Switch Login</h1> <h1 data-i18n="login_heading"> RTL Switch Login</h1>
<form method="post" action="login"> <form method="post" action="login">
<div class="txt_field"> <div class="txt_field">
<input name="pwd" type="password" onclick="removeNote()" required /> <input name="pwd" type="password" autocomplete="current-password" onclick="removeNote()" required />
<span></span> <span></span>
<label>Password</label> <label data-i18n="login_password">Password</label>
</div> </div>
<input type="submit" value="Login"/> <input type="submit" data-i18n="login_login" value="Login"/>
<h3 id="incorrect" style="margin-top: 5em;"></h3> <h3 id="incorrect" style="margin-top: 5em;"></h3>
</form> </form>
</body> </body>
</html> </html>
+17 -16
View File
@@ -2,11 +2,12 @@ var txG = new BigInt64Array(10);
var txB = new BigInt64Array(10); var txB = new BigInt64Array(10);
var rxG = new BigInt64Array(10); var rxG = new BigInt64Array(10);
var rxB = new BigInt64Array(10); var rxB = new BigInt64Array(10);
const linkS = ["Disabled", "Down", "10M", "100M", "1000M", "500M", "10G", "2.5G", "5G"]; 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 pState = new Int8Array(10);
var pIsSFP = new Int8Array(10); var pIsSFP = new Int8Array(10);
var pAdvertised = new Int8Array(10); var pAdvertised = new Int8Array(10);
var numPorts = 0; var numPorts = 0;
function linkText(idx) { var v = linkS[idx]; return typeof v === 'function' ? v() : v; }
var logToPhysPort = new Int8Array(10); var logToPhysPort = new Int8Array(10);
var physToLogPort = new Int8Array(10); var physToLogPort = new Int8Array(10);
var portNames = new Array(10); var portNames = new Array(10);
@@ -21,7 +22,7 @@ function drawPorts() {
d.classList.add('tooltip'); d.classList.add('tooltip');
const s = document.createElement("span"); const s = document.createElement("span");
s.classList.add("tooltiptext"); s.classList.add("tooltiptext");
s.innerHTML = "Tooltip text"; s.innerHTML = t('common_port');
s.id="tt_" + (i+1); s.id="tt_" + (i+1);
const l = document.createElement("object"); const l = document.createElement("object");
d.appendChild(l); d.appendChild(l);
@@ -152,13 +153,13 @@ function update(callback) {
continue; continue;
const portName = p.name || portNames[p.logPort] || ''; const portName = p.name || portNames[p.logPort] || '';
var iHTML = "<table border=\"0\" class=\"tt_table\">"; var iHTML = "<table border=\"0\" class=\"tt_table\">";
if (portName) iHTML += "<tr><td align=\"left\">Name</td><td>:</td><td>" + portName + "</td></tr>"; if (portName) iHTML += "<tr><td align=\"left\">" + t('port_name') + "</td><td>:</td><td>" + portName + "</td></tr>";
if (p.enabled == 0) { if (p.enabled == 0) {
pState[n] = -1; pState[n] = -1;
bgs[0].style.fill = "red"; bgs[0].style.fill = "red";
leds[0].style.fill = "black"; leds[1].style.fill = "black"; leds[0].style.fill = "black"; leds[1].style.fill = "black";
psvg.style.opacity = 0.4; psvg.style.opacity = 0.4;
iHTML += "<tr><td align=\"left\">Status</td><td>:</td><td>Not enabled.</td></tr>"; iHTML += "<tr><td align=\"left\">" + t('port_status') + "</td><td>:</td><td>" + t('port_not_enabled') + "</td></tr>";
iHTML += "</table>"; iHTML += "</table>";
tt.innerHTML = iHTML; tt.innerHTML = iHTML;
} else { } else {
@@ -174,31 +175,31 @@ function update(callback) {
leds[0].style.fill = "black"; leds[1].style.fill = "black"; leds[0].style.fill = "black"; leds[1].style.fill = "black";
psvg.style.opacity = 0.4 psvg.style.opacity = 0.4
} }
iHTML += "<tr><td align=\"left\">Link speed</td><td>:</td><td>" + linkS[p.link + 1] + "</td></tr>"; iHTML += "<tr><td align=\"left\">" + t('port_link_speed') + "</td><td>:</td><td>" + linkText(p.link + 1) + "</td></tr>";
if (p.isSFP) { if (p.isSFP) {
pAdvertised[n] = 0; pAdvertised[n] = 0;
const hasExtendedStatus = p.sfp_options & 0x40; const hasExtendedStatus = p.sfp_options & 0x40;
iHTML += "<tr><td>Vendor</td><td>:</td><td>" + p.sfp_vendor + "</td></tr>"; iHTML += "<tr><td>" + t('port_vendor') + "</td><td>:</td><td>" + p.sfp_vendor + "</td></tr>";
iHTML += "<tr><td>Model</td><td>:</td><td>" + p.sfp_model + "</td></tr>"; iHTML += "<tr><td>" + t('port_model') + "</td><td>:</td><td>" + p.sfp_model + "</td></tr>";
iHTML += "<tr><td>Serial</td><td>:</td><td>" + p.sfp_serial + "</td></tr>"; iHTML += "<tr><td>" + t('port_serial') + "</td><td>:</td><td>" + p.sfp_serial + "</td></tr>";
if (hasExtendedStatus) { if (hasExtendedStatus) {
let txPower = decodeSfpTxPower(p.sfp_txpower, p.sfp_txpower_cal); let txPower = decodeSfpTxPower(p.sfp_txpower, p.sfp_txpower_cal);
let txPowerdBm = convertPowerTodBm(txPower); let txPowerdBm = convertPowerTodBm(txPower);
let rxPower = decodeSfpRxPower(p.sfp_rxpower, p.sfp_rxpower_cal); let rxPower = decodeSfpRxPower(p.sfp_rxpower, p.sfp_rxpower_cal);
let rxPowerdBm = convertPowerTodBm(rxPower); let rxPowerdBm = convertPowerTodBm(rxPower);
iHTML += "<tr><td>Temp</td><td>:</td><td>" + decodeSfpTemp(p.sfp_temp, p.sfp_temp_cal).toFixed(2) + "&#8239;&#8451;</td></tr>"; 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>Vcc</td><td>:</td><td>" + decodeSfpVcc(p.sfp_vcc, p.sfp_vcc_cal).toFixed(2) + "&#8239;V</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>TX-Fault</td><td>:</td><td>" + (Boolean(Number(p.sfp_state) & 0x4)) + "</td></tr>"; iHTML += "<tr><td>" + t('port_tx_fault') + "</td><td>:</td><td>" + (Boolean(Number(p.sfp_state) & 0x4)) + "</td></tr>";
iHTML += "<tr><td>TX-Disabled</td><td>:</td><td>" + (Boolean(Number(p.sfp_state) & 0x80)) + "</td></tr>"; iHTML += "<tr><td>" + t('port_tx_disabled') + "</td><td>:</td><td>" + (Boolean(Number(p.sfp_state) & 0x80)) + "</td></tr>";
iHTML += "<tr><td>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_bias') + "</td><td>:</td><td>" + decodeSfpTxBias(p.sfp_txbias, p.sfp_txbias_cal).toFixed(1) + "&#8239;mA</td></tr>";
iHTML += "<tr><td>TX-Power</td><td>:</td><td>" + txPower.toFixed(3) + "&#8239;mW / " + txPowerdBm.toFixed(2) + "&#8239;dBm</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>RX-Power</td><td>:</td><td>" + rxPower.toFixed(3) + "&#8239;mW / " + rxPowerdBm.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... // Not all devices & modules have LOS pin...
const rx_los_pin = p.sfp_los !== null ? Boolean(Number(p.sfp_los)) : null; 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; const rx_los_module = hasExtendedStatus ? Boolean(Number(p.sfp_state) & 0x2) : null;
if (rx_los_module !== null || rx_los_pin !== null) { if (rx_los_module !== null || rx_los_pin !== null) {
iHTML += `<tr><td>RX-LOS</td><td>:</td><td>${rxLosHTML(rx_los_pin, rx_los_module)}</td></tr>`; iHTML += `<tr><td>` + t('port_rx_los') + `</td><td>:</td><td>${rxLosHTML(rx_los_pin, rx_los_module)}</td></tr>`;
} }
} else { } else {
pAdvertised[n] = parseInt(p.adv, 2); pAdvertised[n] = parseInt(p.adv, 2);
+9 -8
View File
@@ -1,23 +1,24 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<script src="/main.js"></script> <script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>Mirror Configuration</title> <title data-i18n="mirror_title">Mirror Configuration</title>
</head> </head>
<body> <body>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div> <div id="ports"></div>
<h1>Mirror Configuration</h1> <h1 data-i18n="mirror_heading">Mirror Configuration</h1>
<label class="tswitch">Enabled: <input id="me" type="checkbox"></label><br/> <label class="tswitch"><span data-i18n="mirror_enabled">Enabled:</span> <input id="me" type="checkbox"></label><br/>
<label for="mp">Mirroring Port:</label> <input type="number" id="mp" name="mp" min="1" max="9"/> <label for="mp"><span data-i18n="mirror_port">Mirroring Port:</span></label> <input type="number" id="mp" name="mp" min="1" max="9"/>
<h2>Mirrored Ports (TX)</h2> <h2 data-i18n="mirror_tx">Mirrored Ports (TX)</h2>
<div id="mPortsTX"></div> <div id="mPortsTX"></div>
<br /> <br />
<h2>Mirrored Ports (RX)</h2> <h2 data-i18n="mirror_rx">Mirrored Ports (RX)</h2>
<div id="mPortsRX"></div> <div id="mPortsRX"></div>
<br/> <input style="width:15%;" class="action" id="mirror_sub" onclick="mirrorSub();" type="button" value="Update / Create"> <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" value="Disable Mirroring"> <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.js"></script>
<script src="/mirror_sub.js"></script> <script src="/mirror_sub.js"></script>
</div> </div>
+2 -2
View File
@@ -2,7 +2,7 @@ async function mirrorSub() {
var cmd = "mirror "; var cmd = "mirror ";
var mp=document.getElementById('mp').value var mp=document.getElementById('mp').value
if (!mp) { if (!mp) {
alert("Set Mirroring Port first"); alert(t('mirror_set_port_first'));
return; return;
} }
document.getElementById(mirrors[0]+mp).checked=false;document.getElementById(mirrors[1]+mp).checked=false; document.getElementById(mirrors[0]+mp).checked=false;document.getElementById(mirrors[1]+mp).checked=false;
@@ -16,7 +16,7 @@ async function mirrorSub() {
cmd = cmd + ` ${i}r`; cmd = cmd + ` ${i}r`;
} }
if (cmd.length < 10) { if (cmd.length < 10) {
alert("Select Mirrored Ports"); alert(t('mirror_select_ports'));
return; return;
} }
try { try {
+19 -11
View File
@@ -1,12 +1,20 @@
document.getElementById('sidebar').innerHTML = document.getElementById('sidebar').innerHTML =
"<ul><li><a href='index.html'>Overview</a></li>" "<ul><li><a href='index.html' data-i18n='nav_overview'>Overview</a></li>"
+ "<li><a href='ports.html'>Port Configuration</a></li>" + "<li><a href='ports.html' data-i18n='nav_port_config'>Port Configuration</a></li>"
+ "<li><a href='stat.html'>Port Statistics</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='vlan.html' >VLAN</a></li>"
+ "<li><a href='l2.html'>L2 Configuration</a></li>" + "<li><a href='l2.html' data-i18n='nav_l2'>L2 Configuration</a></li>"
+ "<li><a href='mirror.html'>Mirroring</a></li>" + "<li><a href='mirror.html' data-i18n='nav_mirror'>Mirroring</a></li>"
+ "<li><a href='lag.html'>Link Aggregation</a></li>" + "<li><a href='lag.html' data-i18n='nav_lag'>Link Aggregation</a></li>"
+ "<li><a href='eee.html'>EEE</a></li>" + "<li><a href='eee.html' data-i18n='nav_eee'>EEE</a></li>"
+ "<li><a href='bandwidth.html'>Bandwidth Limits</a></li>" + "<li><a href='bandwidth.html' data-i18n='nav_bandwidth'>Bandwidth Limits</a></li>"
+ "<li><a href='system.html'>System Settings</a></li>" + "<li><a href='system.html' data-i18n='nav_system'>System Settings</a></li>"
+ "<li><a href='update.html'>Firmware Update</a></li></ul>"; + "<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);
});
});
+5 -4
View File
@@ -1,19 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<script src="/main.js"></script> <script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>FreeSwitchOS Port Configuration</title> <title data-i18n="port_title">FreeSwitchOS Port Configuration</title>
</head> </head>
<body> <body>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div> <div id="ports"></div>
<h1>Port Configuration</h1> <h1 data-i18n="port_heading">Port Configuration</h1>
<form id="vform" action="/vlan.html"> <form id="vform" action="/vlan.html">
<table id="speedtable"> <table id="speedtable">
<tr> <th>Port</th> <th>Name</th> <th>Current Link Speed</th><th>Set Speed</th><th>Disabled</th><th>Apply</th></tr> <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_set_speed">Set Speed</th><th data-i18n="port_col_disabled">Disabled</th><th data-i18n="port_col_apply">Apply</th></tr>
</table> </table>
<h2 style="margin-top:3em">Configure Maximum Frame Size (MTU) forwarded at Port</h2> <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 id="mtutable" style="margin-top:1em">
</table> </table>
<script src="/ports.js"></script> <script src="/ports.js"></script>
+12 -12
View File
@@ -4,13 +4,13 @@ function createPortTable() {
var tbl = document.getElementById('speedtable'); var tbl = document.getElementById('speedtable');
if (tbl.rows.length <= 2 && numPorts) { if (tbl.rows.length <= 2 && numPorts) {
const sSelect = '<select name="speed_sel" id="speed_sel">' const sSelect = '<select name="speed_sel" id="speed_sel">'
+ '<option value="auto">Auto</option>' + '<option value="auto">' + t('port_auto') + '</option>'
+ '<option value="2g5">2500MBit/Full</option>' + '<option value="2g5">' + t('port_2500m') + '</option>'
+ '<option value="1g">1000MBit/Full</option>' + '<option value="1g">' + t('port_1000m') + '</option>'
+ '<option value="100m full">100MBit/Full</option>' + '<option value="100m full">' + t('port_100m_f') + '</option>'
+ '<option value="100m half">100MBit/Half</option>' + '<option value="100m half">' + t('port_100m_h') + '</option>'
+ '<option value="10m full">10MBit/Full</option>' + '<option value="10m full">' + t('port_10m_f') + '</option>'
+ '<option value="10m half">10MBit/Half</option>' + '<option value="10m half">' + t('port_10m_h') + '</option>'
+ '</select>'; + '</select>';
const dSwitch = '<input type="checkbox" id="disable_port" onchange="portOnOff();">' const dSwitch = '<input type="checkbox" id="disable_port" onchange="portOnOff();">'
for (let i = 1; i <= numPorts; i++) { for (let i = 1; i <= numPorts; i++) {
@@ -18,14 +18,14 @@ function createPortTable() {
continue; continue;
console.log("Table row: " + i + "pState: " + pState[i-2]); console.log("Table row: " + i + "pState: " + pState[i-2]);
const tr = tbl.insertRow(); const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(`Port ${i}`)); let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + i));
let portName = portNames[physToLogPort[i-1]] || ''; let portName = portNames[physToLogPort[i-1]] || '';
td = tr.insertCell(); td.appendChild(document.createTextNode(portName)); td = tr.insertCell(); td.appendChild(document.createTextNode(portName));
td = tr.insertCell(); td.innerHTML = linkS[pState[i] + 1]; td = tr.insertCell(); td.innerHTML = linkText(pState[i] + 1);
td = tr.insertCell(); td.innerHTML = sSelect.replaceAll("speed_sel", "speed_sel_" + i); td = tr.insertCell(); td.innerHTML = sSelect.replaceAll("speed_sel", "speed_sel_" + i);
td = tr.insertCell(); td.innerHTML = dSwitch.replaceAll("disable_port", "disable_port_" + i) td = tr.insertCell(); td.innerHTML = dSwitch.replaceAll("disable_port", "disable_port_" + i)
.replace("portOnOff()", "portOnOff(" + i + ")"); .replace("portOnOff()", "portOnOff(" + i + ")");
var button = '<button type="button" style="margin: 0 0 0 24px" onclick="applySpeed(' + i + ');">Apply</button>'; var button = '<button type="button" style="margin: 0 0 0 24px" onclick="applySpeed(' + i + ');">' + t('port_apply') + '</button>';
td = tr.insertCell(); td = tr.insertCell();
td.innerHTML = button; td.innerHTML = button;
} }
@@ -55,7 +55,7 @@ function createPortTable() {
tr = tbl.insertRow(); tr = tbl.insertRow();
for (let i = 1; i <= numPorts; i++) { for (let i = 1; i <= numPorts; i++) {
let td = tr.insertCell(); let td = tr.insertCell();
td.innerHTML = '<button type="button" style="margin: 0 0 0 24px" onclick="applyMTU(' + i + ');">Apply</button>'; td.innerHTML = '<button type="button" style="margin: 0 0 0 24px" onclick="applyMTU(' + i + ');">' + t('port_apply') + '</button>';
} }
} }
} }
@@ -68,7 +68,7 @@ function updatePortTable() {
for (let i = 1; i <= numPorts ; i++) { for (let i = 1; i <= numPorts ; i++) {
if (pIsSFP[i-1]) if (pIsSFP[i-1])
continue; continue;
tbl.rows[i].cells[2].innerHTML = `${linkS[pState[i-1]+1]}`; tbl.rows[i].cells[2].innerHTML = linkText(pState[i-1]+1);
if (!clicked[i] && pState[i - 1] < 0) { if (!clicked[i] && pState[i - 1] < 0) {
document.getElementById('speed_sel_' + i).disabled = true; document.getElementById('speed_sel_' + i).disabled = true;
document.getElementById('disable_port_' + i).checked = true; document.getElementById('disable_port_' + i).checked = true;
+6 -5
View File
@@ -1,8 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<script src="/main.js"></script> <script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>FreeSwitchOS Port Statistics</title> <title data-i18n="stat_title">FreeSwitchOS Port Statistics</title>
<style> <style>
.popup { .popup {
display: none; display: none;
@@ -31,14 +32,14 @@
<div id="ports"></div> <div id="ports"></div>
<div id="popup" class="popup"> <div id="popup" class="popup">
<div class="popup-content"> <div class="popup-content">
<h2>Detailed Port Statistics</h2> <h2 data-i18n="stat_detailed">Detailed Port Statistics</h2>
<div id="popup_text"></div> <div id="popup_text"></div>
<button id="closePopup" class="action">Close</button> <button id="closePopup" class="action" data-i18n="stat_close">Close</button>
</div> </div>
</div> </div>
<h1>Port Statistics</h1> <h1 data-i18n="stat_heading">Port Statistics</h1>
<table id="statstable"> <table id="statstable">
<tr> <th>Port</th> <th>Name</th> <th>link</th> <th>TX Good</th> <th>TX Bad</th> <th>RX Good</th> <th>RX Bad</th> <th> All Counters </th></tr> <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> <script src="/stat.js"></script>
</table> </table>
</div> </div>
+19 -19
View File
@@ -114,7 +114,7 @@ function getCounters(port) {
const s = JSON.parse(xhttp.responseText); const s = JSON.parse(xhttp.responseText);
console.log("Counters: ", JSON.stringify(s)); console.log("Counters: ", JSON.stringify(s));
const ptext = document.getElementById('popup_text'); const ptext = document.getElementById('popup_text');
var t = "<table style='width:100%'> <tr> <th>Counter</th> <th>Value</th> <th>Counter</th> <th>Value</th></tr> <tr>"; 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); console.log("Counter 0: ", BigInt(s[0]).toString(), " length: ", s.length);
var c = 0; var c = 0;
for (i = 0; i < mib_counters.length; i += 4) { for (i = 0; i < mib_counters.length; i += 4) {
@@ -125,28 +125,28 @@ function getCounters(port) {
} }
var count = BigInt(s[i/4]); var count = BigInt(s[i/4]);
if (mib_counters[i+1] == 8) { if (mib_counters[i+1] == 8) {
t += "<td>" + mib_counters[i] + "</td><td>" + count.toString() + "</td>"; tableHtml += "<td>" + mib_counters[i] + "</td><td>" + count.toString() + "</td>";
c += 1; c += 1;
} else if (mib_counters[i+1] == 4) { } else if (mib_counters[i+1] == 4) {
if (mib_counters[i] != "") { if (mib_counters[i] != "") {
t += "<td>" + mib_counters[i] + "</td><td>" + (count >> 32n).toString() + "</td>"; tableHtml += "<td>" + mib_counters[i] + "</td><td>" + (count >> 32n).toString() + "</td>";
c += 1; c += 1;
} }
if (c == 2) { if (c == 2) {
t += "</tr> <tr>"; tableHtml += "</tr> <tr>";
c = 0; c = 0;
} }
if (mib_counters[i+2] != "") { if (mib_counters[i+2] != "") {
t += "<td>" + mib_counters[i+2] + "</td><td>" + (count & 4294967295n).toString() + "</td>"; tableHtml += "<td>" + mib_counters[i+2] + "</td><td>" + (count & 4294967295n).toString() + "</td>";
c += 1; c += 1;
} }
} }
if (c == 2) { if (c == 2) {
t += "</tr> <tr>"; tableHtml += "</tr> <tr>";
c = 0; c = 0;
} }
} }
ptext.innerHTML = t + "</tr></table>"; ptext.innerHTML = tableHtml + "</tr></table>";
popup.style.display = 'flex'; popup.style.display = 'flex';
} }
}; };
@@ -162,25 +162,25 @@ function fillStats() {
if (tbl.rows.length > 1) { if (tbl.rows.length > 1) {
for (let i = 0; i < numPorts; i++) { for (let i = 0; i < numPorts; i++) {
console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] +1]); console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] +1]);
tbl.rows[i+1].cells[2].innerHTML = `${linkS[pState[i]+1]}`; tbl.rows[i+1].cells[2].innerHTML = linkText(pState[i]+1);
tbl.rows[i+1].cells[3].innerHTML = `${txG[i]} pkts`; tbl.rows[i+1].cells[3].innerHTML = `${txG[i]}` + t('common_pkts');
tbl.rows[i+1].cells[4].innerHTML = `${txB[i]} pkts`; tbl.rows[i+1].cells[4].innerHTML = `${txB[i]}` + t('common_pkts');
tbl.rows[i+1].cells[5].innerHTML = `${rxG[i]} pkts`; tbl.rows[i+1].cells[5].innerHTML = `${rxG[i]}` + t('common_pkts');
tbl.rows[i+1].cells[6].innerHTML = `${rxB[i]} pkts`; tbl.rows[i+1].cells[6].innerHTML = `${rxB[i]}` + t('common_pkts');
} }
} else { } else {
for (let i = 0; i < numPorts; i++) { for (let i = 0; i < numPorts; i++) {
console.log("Table row: " + i); console.log("Table row: " + i);
const tr = tbl.insertRow(); const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(`Port ${i+1}`)); let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i+1)));
let portName = portNames[physToLogPort[i]] || ''; let portName = portNames[physToLogPort[i]] || '';
td = tr.insertCell(); td.appendChild(document.createTextNode(portName)); td = tr.insertCell(); td.appendChild(document.createTextNode(portName));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${linkS[pState[i]+1]}`)); td = tr.insertCell(); td.appendChild(document.createTextNode(linkText(pState[i]+1)));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]} pkts`)); td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]}` + t('common_pkts')));
td = tr.insertCell();td.appendChild(document.createTextNode(`${txB[i]} pkts`)); td = tr.insertCell();td.appendChild(document.createTextNode(`${txB[i]}` + t('common_pkts')));
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxG[i]} pkts`)); td = tr.insertCell();td.appendChild(document.createTextNode(`${rxG[i]}` + t('common_pkts')));
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxB[i]} 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 + ');">Show</button>'; var button = '<button type="button" style="margin: 0 0 0 24px" onclick="getCounters(' + i + ');">' + t('stat_show') + '</button>';
td = tr.insertCell(); td.innerHTML = button; td = tr.insertCell(); td.innerHTML = button;
} }
} }
+6
View File
@@ -82,6 +82,7 @@ object, img {
.isNOK{ color: #900;} .isNOK{ color: #900;}
.isOK{ color: #090;} .isOK{ color: #090;}
.ip{padding:8px 16px;margin-bottom: 1em;margin-left: 1em} .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;} .row {display: flex;}
.rcol {flex: 90%;} .rcol {flex: 90%;}
.lcol {flex: 10%;} .lcol {flex: 10%;}
@@ -164,3 +165,8 @@ margin: 30px 0;
select { text-align-last: right; font-family: monospace} select { text-align-last: right; font-family: monospace}
option { direction: rtl; font-family: sans-serif} option { direction: rtl; font-family: sans-serif}
#vlanTable td { text-align: left; } #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}
+46 -23
View File
@@ -1,8 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>System Settings</title> <title data-i18n="sys_title">System Settings</title>
<style> <style>
.tab-bar { display: flex; border-bottom: 2px solid #226; margin-bottom: 0; margin-left: 16%; padding: 1px 16px; padding-bottom: 0; } .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 { padding: 10px 20px; background-color: #ddf; border: none; cursor: pointer; font-size: 1em; border-radius: 8px 8px 0 0; margin-right: 4px; }
@@ -14,57 +15,79 @@
</head> </head>
<body> <body>
<div class="tab-bar"> <div class="tab-bar">
<button class="tab-btn active" onclick="openTab(event, 'system-tab')">System</button> <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')">Advanced</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')">Console</button> <button class="tab-btn" onclick="openTab(event, 'console-tab')" data-i18n="sys_tab_console">Console</button>
</div> </div>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div> <div id="ports"></div>
<div id="system-tab" class="tab-content active"> <div id="system-tab" class="tab-content active">
<h1>System Settings</h1> <h1 data-i18n="sys_heading">System Settings</h1>
<div class="row"> <div class="row">
<div class="lcol"> <label for="ip">IP address:</label></div> <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 class="rcol"> <input id="ip" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
</div> </div>
<div class="row"> <div class="row">
<div class="lcol"> <label for="netmask">Netmask:</label></div> <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 class="rcol"><input id="netmask" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
</div> </div>
<div class="row"> <div class="row">
<div class="lcol"> <label for="gw">Gateway:</label></div> <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 class="rcol"><input id="gw" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
</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/> <br/>
When updating the above settings, remember to point your browser to the new IP afterwards:<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" value="Update Settings"><br/> <input style="width:40%;" class="action" id="ip_sub" onclick="ipSub();" type="button" data-i18n="sys_update" value="Update Settings"><br/>
<br/> <br/>
Save all current settings to Flash:<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" value="Save Settings to Flash"> <input style="width:40%;" class="action" id="flash_sub" onclick="flashSave();" type="button" data-i18n="sys_save" value="Save Settings to Flash">
</div> </div>
<div id="advanced-tab" class="tab-content"> <div id="advanced-tab" class="tab-content">
<h1>Advanced Settings</h1> <h1 data-i18n="sys_advanced">Advanced Settings</h1>
<div class="lcol"> <label for="config_display">Startup configuration:</label></div> <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> <textarea id="config_display" rows="8" cols="60"></textarea>
<br/><br/> <br/><br/>
Be careful when saving the directly edited startup configuration, you can lock yourself out:<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" value="Clear Startup Config"> <input style="width:40%;" class="action" id="clear_config" onclick="clearConfig();" type="button" data-i18n="sys_clear_config" value="Clear Startup Config">
<br/> <br/>
<input style="width:40%;" class="action" id="flash_startup_sub" onclick="flashStartupSave();" type="button" value="Save Startup Settings to Flash"> <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/> <br/>
<input style="width:40%;" class="action" id="switch_reset" onclick="resetSwitch();" type="button" value="Reset Switch"> <input style="width:40%;" class="action" id="switch_reset" onclick="resetSwitch();" type="button" data-i18n="sys_reset" value="Reset Switch">
</div> </div>
<div id="console-tab" class="tab-content"> <div id="console-tab" class="tab-content">
<h1>Console Command</h1> <h1 data-i18n="sys_console">Console Command</h1>
<label for="console_command">Enter command:</label> <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 type="text" id="console_cmd" name="console_cmd" style="width:40%;">
<input style="width:20%;" class="action" id="cmd_sub" onclick="cmdSub();" type="button" value="Send Command"><br/> <input style="width:20%;" class="action" id="cmd_sub" onclick="cmdSub();" type="button" data-i18n="sys_send_cmd" value="Send Command"><br/>
<br/><br/> <br/><br/>
Be careful when entering console commands, you can lock yourself out!<br/> <span data-i18n="sys_console_warn">Be careful when entering console commands, you can lock yourself out!</span><br/>
</div> </div>
+61 -3
View File
@@ -2,9 +2,14 @@ var systemInterval = Number();
var isSaving = false; var isSaving = false;
const ips = ["ip", "netmask", "gw"]; const ips = ["ip", "netmask", "gw"];
function changeLang() {
var lang = document.getElementById('lang-select').value;
setLang(lang);
}
function checkIp(ip) { function checkIp(ip) {
const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/; const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipv4.test(ip)) {alert(`Invalid ip:${ip}`); return false }; if (!ipv4.test(ip)) {alert(t('sys_invalid_ip') + ip); return false };
return true; return true;
} }
@@ -43,6 +48,14 @@ async function cmdSub() {
} }
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) { async function sendConfig(c) {
if (isSaving) return; if (isSaving) return;
isSaving = true; isSaving = true;
@@ -118,6 +131,9 @@ function fetchIP() {
document.getElementById("ip").value=s.ip_address; document.getElementById("ip").value=s.ip_address;
document.getElementById("netmask").value=s.ip_netmask; document.getElementById("netmask").value=s.ip_netmask;
document.getElementById("gw").value=s.ip_gateway; document.getElementById("gw").value=s.ip_gateway;
document.getElementById("hostname").value=s.hostname;
document.getElementById("model").textContent=s.hw_ver;
loadMgmtVlan();
clearInterval(systemInterval); clearInterval(systemInterval);
// Fetch and populate the config textbox // Fetch and populate the config textbox
fetchConfig().then((configText) => { fetchConfig().then((configText) => {
@@ -136,15 +152,57 @@ function fetchIP() {
} }
function resetSwitch() { function resetSwitch() {
if (!confirm('Are you sure you want to reset the switch?')) { if (!confirm(t('sys_reset_confirm'))) {
return; return;
} }
fetch('/reset', { method: 'GET' }).catch(() => {}); fetch('/reset', { method: 'GET' }).catch(() => {});
setTimeout(() => { setTimeout(() => {
alert('Switch is resetting. Please wait and refresh the page.'); alert(t('sys_resetting'));
}, 3000); }, 3000);
} }
window.addEventListener("load", function() { window.addEventListener("load", function() {
var langSel = document.getElementById('lang-select');
if (langSel) langSel.value = rtlLang;
systemInterval = setInterval(fetchIP, 1000); 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; });
}
+5 -4
View File
@@ -1,18 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>Firmware update</title> <title data-i18n="update_title">Firmware update</title>
</head> </head>
<body> <body>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;width:40%;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;width:40%;">
<h1>Firmware Update</h1> <h1 data-i18n="update_heading">Firmware Update</h1>
<form enctype="multipart/form-data" action="/upload" method="POST"> <form enctype="multipart/form-data" action="/upload" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000000" /> <input type="hidden" name="MAX_FILE_SIZE" value="1000000" />
Choose a firmware update file to upload: <br/> <br/> <span data-i18n="update_instruction">Choose a firmware update file to upload:</span> <br/> <br/>
<input name="uploadedfile" type="file" accept=".bin" /><br /> <input name="uploadedfile" type="file" accept=".bin" /><br />
<input style="margin-top:3em" type="submit" value="Upload File" /> <input style="margin-top:3em" type="submit" data-i18n="update_upload" value="Upload File" />
</form> </form>
<script src="/navigation.js"></script> <script src="/navigation.js"></script>
</body> </body>
+22 -21
View File
@@ -1,52 +1,53 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<script src="/main.js"></script> <script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<title>FreeSwitchOS VLAN Configuration</title> <title data-i18n="vlan_title">FreeSwitchOS VLAN Configuration</title>
</head> </head>
<body> <body>
<nav id="sidebar"></nav> <nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;"> <div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div> <div id="ports"></div>
<h1>VLAN Configuration</h1> <h1 data-i18n="vlan_heading">VLAN Configuration</h1>
<form id="vform" action="/vlan.html"> <form id="vform" action="/vlan.html">
<div> <div>
<label for="vlanSelect">VLAN auswählen:</label> <label for="vlanSelect" data-i18n="vlan_select">VLAN Select:</label>
<select id="vlanSelect" style="margin: 0 0 0 8px"> <select id="vlanSelect" style="margin: 0 0 0 8px">
<option value="" disabled selected>— VLAN wählen</option> <option value="" disabled selected data-i18n="vlan_choose">— VLAN Choose</option>
</select> </select>
</div> </div>
<br/> <br/>
<div> <div>
<label for="vid">VLAN ID:</label> <label for="vid" data-i18n="vlan_id">VLAN ID:</label>
<input type="number" min="1" max="4094" id="vid" name="vid"> <input type="number" min="1" max="4094" id="vid" name="vid">
<button type="button" style="margin: 0 0 0 24px" onclick="fetchVLAN();">Get Configuration</button> <button type="button" style="margin: 0 0 0 24px" onclick="fetchVLAN();" data-i18n="vlan_get_config">Get Configuration</button>
</div> </div>
<br/><br/> <br/><br/>
<label for="vname">VLAN Name:</label> <label for="vname" data-i18n="vlan_name">VLAN Name:</label>
<input type="text" id="vname" name="vname"><br><br> <input type="text" id="vname" name="vname"><br><br>
<br/> <br/>
<h2>Tagged Ports</h2> <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);">Select all</button></div> <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>Untagged Ports</h2> <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);">Select all</button> </div> <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>Use as default VLAN for incoming traffic (PVID)</h2> <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);">Select all</button> </div> <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> <script src="/vlan.js"></script>
<br/> <input style="width:40%;" class="action" id="vlan_sub" onclick="vlanSub();" type="button" value="Update / Create"> <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> <script src="/vlan_sub.js"></script>
</form> </form>
<h2>Configured VLANs</h2> <h2 data-i18n="vlan_configured">Configured VLANs</h2>
<table id="vlanTable" style="width:90%"> <table id="vlanTable" style="width:90%">
<thead> <thead>
<tr> <tr>
<th>VLAN</th> <th>VLAN</th>
<th>Name</th> <th data-i18n="vlan_col_name">Name</th>
<th>Member Ports</th> <th data-i18n="vlan_col_member">Member Ports</th>
<th>Tagged Ports</th> <th data-i18n="vlan_col_tagged">Tagged Ports</th>
<th>Untagged Ports</th> <th data-i18n="vlan_col_untagged">Untagged Ports</th>
<th>PVID Ports</th> <th data-i18n="vlan_col_pvid">PVID Ports</th>
<th>Delete</th> <th data-i18n="vlan_col_delete">Delete</th>
</tr> </tr>
</thead> </thead>
<tbody id="vlanTableBody"> <tbody id="vlanTableBody">
+4 -4
View File
@@ -76,7 +76,7 @@ function fetchVLAN() {
}; };
var v=document.getElementById('vid').value var v=document.getElementById('vid').value
if (!v) { if (!v) {
alert("Set VLAN ID first"); alert(t('vlan_set_id_first'));
return; return;
} }
xhttp.open("GET", `/vlan.json?vid=${v}`, true); xhttp.open("GET", `/vlan.json?vid=${v}`, true);
@@ -110,7 +110,7 @@ async function loadVlanTable() {
var resp; var resp;
try { resp = await fetch('/vlanlist'); } catch(e) { return; } try { resp = await fetch('/vlanlist'); } catch(e) { return; }
if (!resp.ok) return; if (!resp.ok) return;
var vlans = await resp.json(); var vlans = (await resp.json()).vlan || [];
for (var i = 0; i < vlans.length; i++) { for (var i = 0; i < vlans.length; i++) {
var v = vlans[i]; var v = vlans[i];
var vresp; var vresp;
@@ -161,7 +161,7 @@ async function loadVlanTable() {
} }
function deleteVlan(id) { function deleteVlan(id) {
if (!confirm('Delete VLAN ' + id + '?')) return; if (!confirm(t('vlan_delete_confirm') + id + '?')) return;
fetch('/cmd', { method: 'POST', body: 'vlan ' + id + ' d' }) fetch('/cmd', { method: 'POST', body: 'vlan ' + id + ' d' })
.then(function() { refreshVlanViews(); }) .then(function() { refreshVlanViews(); })
.catch(function(err) { console.error('Delete failed:', err); }); .catch(function(err) { console.error('Delete failed:', err); });
@@ -181,7 +181,7 @@ function loadVlanList() {
sel.style.display = 'none'; sel.style.display = 'none';
return; return;
} }
var vlans = JSON.parse(this.responseText); var vlans = JSON.parse(this.responseText).vlan || [];
if (!vlans.length) { if (!vlans.length) {
sel.style.display = 'none'; sel.style.display = 'none';
return; return;
+1 -1
View File
@@ -3,7 +3,7 @@ async function vlanSub() {
var cmd = "vlan "; var cmd = "vlan ";
var v=document.getElementById('vid').value var v=document.getElementById('vid').value
if (!v) { if (!v) {
alert("Set VLAN ID first"); alert(t('vlan_set_id_first'));
return; return;
} }
cmd = cmd + v; cmd = cmd + v;
+33 -28
View File
@@ -101,21 +101,6 @@ uint8_t find_entry(__xdata uint8_t *e)
} }
char strcmp(__xdata uint8_t *c, __code uint8_t * __xdata d)
{
uint8_t i = 0;
while (d[i] && (d[i] == c[i]))
i++;
if (c[i] < d[i])
return -1;
else if (c[i] > d[i])
return 1;
return 0;
}
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 uint8_t * __xdata code_str_p)
{ {
uint8_t u, c; uint8_t u, c;
@@ -189,7 +174,8 @@ bool is_word_x(__xdata uint8_t *lhs_str_p, __xdata uint8_t *rhs_str_p)
c = *rhs_str_p++; c = *rhs_str_p++;
if (c == '\0') { if (c == '\0') {
if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r') /* ';' separates cookies in a Cookie header, so it ends a value too. */
if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r' && u != ';')
return false; return false;
return true; return true;
} }
@@ -219,28 +205,28 @@ uint8_t parse_short(__xdata uint8_t *p)
void send_not_found(void) void send_not_found(void)
{ {
slen = strtox(outbuf, "HTTP/1.1 404 Not found\r\nContent-Type: text/html\r\n\r\n" \ slen = strtox(outbuf, "HTTP/1.1 404 Not found\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n" \
"<!DOCTYPE HTML PUBLIC>\n<title>404 Not Found</title>\n<h1>Not Found</h1>\n"); "<!DOCTYPE HTML PUBLIC>\n<title>404 Not Found</title>\n<h1>Not Found</h1>\n");
} }
void send_bad_request(void) void send_bad_request(void)
{ {
slen = strtox(outbuf, "HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\n\r\n" \ slen = strtox(outbuf, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n" \
"<!DOCTYPE HTML PUBLIC>\n<title>400 Bad Request</title>\n<h1>Bad Request</h1>\n"); "<!DOCTYPE HTML PUBLIC>\n<title>400 Bad Request</title>\n<h1>Bad Request</h1>\n");
} }
void send_to_login(void) void send_to_login(void)
{ {
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\n" \ slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\n" \
"Location: login.html\r\n\r\n"); "Location: login.html\r\n\r\n");
} }
void send_unauthorized(void) void send_unauthorized(void)
{ {
slen = strtox(outbuf, "HTTP/1.1 401 Unauthorized\r\n\r\n"); slen = strtox(outbuf, "HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
} }
@@ -267,14 +253,27 @@ __xdata uint8_t *scan_header(__xdata uint8_t *p)
break; break;
if (is_word(p, "\nContent-Type:")) if (is_word(p, "\nContent-Type:"))
content_type = p + 15; content_type = p + 15;
else if (is_word(p, "\nCookie:")) else if (is_word(p, "\nCookie:")) {
session = p + 17; /* Scan for the "session" key: the header may hold several
* cookies in any order. Match "session" not "session=" -
* is_word() requires a separator after the match and '=' is
* one, so this also rejects a longer key like "sessionx". */
__xdata uint8_t *c = p + 8; /* past "\nCookie:" */
while (*c && *c != '\r' && *c != '\n') {
if (is_word(c, "session")) {
session = c + 8; /* past "session=" */
break;
}
c++;
}
}
} }
if (content_type && is_word(content_type, "multipart/form-data; boundary")) { if (content_type && is_word(content_type, "multipart/form-data; boundary")) {
dbg_string("\nFound multipart\n"); dbg_string("\nFound multipart\n");
content_type += 30; content_type += 30;
uint8_t i = 0; uint8_t i = 0;
while (content_type[i] != '\r' && content_type[i] != '\n') { while (i < (sizeof(boundary) - 5) &&
content_type[i] != '\r' && content_type[i] != '\n') {
boundary[i + 4] = content_type[i]; boundary[i + 4] = content_type[i];
i++; i++;
} }
@@ -484,14 +483,14 @@ void handle_post(void)
read_reg_timer(&last_session_use); read_reg_timer(&last_session_use);
gen_random_bytes(session_id, SESSION_ID_LENGTH); gen_random_bytes(session_id, SESSION_ID_LENGTH);
session_id[SESSION_ID_LENGTH] = '\0'; session_id[SESSION_ID_LENGTH] = '\0';
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nLocation: index.html\r\n" \ slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: index.html\r\n" \
"Set-Cookie: session="); "Set-Cookie: session=");
for (register uint8_t i = 0; i < SESSION_ID_LENGTH; i++) for (register uint8_t i = 0; i < SESSION_ID_LENGTH; i++)
outbuf[slen++] = session_id[i]; outbuf[slen++] = session_id[i];
slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n"); slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n");
} else { } else {
dbg_string("Password invalid!\n"); dbg_string("Password invalid!\n");
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nLocation: login.html\r\n\r\n"); slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: login.html\r\n\r\n");
} }
return; return;
} else if (s->tstate == TSTATE_MULTIPART || is_word(request_path, "upload") || is_word(request_path, "config")) { } else if (s->tstate == TSTATE_MULTIPART || is_word(request_path, "upload") || is_word(request_path, "config")) {
@@ -536,7 +535,7 @@ void handle_post(void)
send_not_found(); send_not_found();
return; return;
} }
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\n\r\n"); slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n");
return; return;
bad_request: bad_request:
send_bad_request(); send_bad_request();
@@ -641,7 +640,7 @@ void httpd_appcall(void)
p += 4; p += 4;
scan_header(p); scan_header(p);
__xdata uint8_t *q = p; __xdata uint8_t *q = p;
while (!is_separator(*p)) while (*p && !is_separator(*p))
p++; p++;
*p = '\0'; *p = '\0';
dbg_string_x(q); dbg_string_x(q);
@@ -714,7 +713,13 @@ void httpd_appcall(void)
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Type: "); slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Type: ");
slen += strtox(outbuf + slen, mime_strings[f_data[entry].mime]); slen += strtox(outbuf + slen, mime_strings[f_data[entry].mime]);
slen += strtox(outbuf + slen, "; charset=UTF-8\r\nCache-Control: max-age=60, must-revalidate\r\nAccess-Control-Allow-Origin: *\r\nContent-Security-Policy: style-src 'self' 'unsafe-inline'\r\n\r\n"); /* 'unsafe-inline' is needed for the inline onclick handlers
* and the inline <script> on login.html. Connection: close is
* required because this httpd closes the connection after every
* 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");
len_left = f_data[entry].len; len_left = f_data[entry].len;
if (len_left > (TCP_OUTBUF_SIZE - slen)) { if (len_left > (TCP_OUTBUF_SIZE - slen)) {
+38 -27
View File
@@ -26,6 +26,7 @@
extern __code const struct machine machine; extern __code const struct machine machine;
extern __xdata uint8_t outbuf[TCP_OUTBUF_SIZE]; extern __xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
extern __xdata uint16_t slen; extern __xdata uint16_t slen;
extern __xdata uint16_t management_vlan;
extern __xdata uint16_t cont_len; extern __xdata uint16_t cont_len;
extern __xdata uint32_t cont_addr; extern __xdata uint32_t cont_addr;
extern __code uint8_t * __code hex; extern __code uint8_t * __code hex;
@@ -45,7 +46,7 @@ extern __xdata char sfp_module_model[2][17];
extern __xdata char sfp_module_serial[2][17]; extern __xdata char sfp_module_serial[2][17];
extern __xdata uint8_t sfp_options[2]; extern __xdata uint8_t sfp_options[2];
__code uint8_t * __code HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"; __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\nContent-Type: text/plain\r\n\r\n"; __code uint8_t * __code HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n";
// Convert uint8_t to ascii HEX char push on html-buffer. // Convert uint8_t to ascii HEX char push on html-buffer.
@@ -252,6 +253,12 @@ void send_basic_info(void)
byte_to_html(uip_ethaddr.addr[3]); char_to_html(':'); byte_to_html(uip_ethaddr.addr[3]); char_to_html(':');
byte_to_html(uip_ethaddr.addr[4]); char_to_html(':'); byte_to_html(uip_ethaddr.addr[4]); char_to_html(':');
byte_to_html(uip_ethaddr.addr[5]); byte_to_html(uip_ethaddr.addr[5]);
slen += strtox(outbuf + slen, "\",\"hostname\":\"");
{
__xdata char *hp = hostname; /* sanitized on ingest, emit verbatim */
while (*hp)
char_to_html(*hp++);
}
slen += strtox(outbuf + slen, "\",\"sw_ver\":\""); slen += strtox(outbuf + slen, "\",\"sw_ver\":\"");
slen += strtox(outbuf + slen, VERSION_SW); slen += strtox(outbuf + slen, VERSION_SW);
slen += strtox(outbuf + slen, "\",\"build_date\":\""); slen += strtox(outbuf + slen, "\",\"build_date\":\"");
@@ -660,52 +667,53 @@ void send_status(void)
slen += strtox(outbuf + slen, "\""); slen += strtox(outbuf + slen, "\"");
if (machine.is_sfp[i]) { if (machine.is_sfp[i]) {
uint8_t sfp = machine.is_sfp[i] - 1;
slen += strtox(outbuf + slen, ",\"isSFP\":1,\"enabled\":"); slen += strtox(outbuf + slen, ",\"isSFP\":1,\"enabled\":");
if (!(sfp_pins_last & (0x1 << ((machine.is_sfp[i] - 1) << 2)))) { if (!(sfp_pins_last & (0x1 << (sfp << 2)))) {
bool_to_html(1); bool_to_html(1);
slen += strtox(outbuf + slen,",\"sfp_options\":\"0x"); slen += strtox(outbuf + slen,",\"sfp_options\":\"0x");
byte_to_html(sfp_options[machine.is_sfp[i]-1]); byte_to_html(sfp_options[sfp]);
if (sfp_options[machine.is_sfp[i]-1] & 0x40) { if (sfp_options[sfp] & 0x40) {
slen += strtox(outbuf + slen,"\",\"sfp_temp\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_temp\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 224, 2); sfp_send_data(sfp, 224, 2);
slen += strtox(outbuf + slen,"\",\"sfp_vcc\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_vcc\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 226, 2); sfp_send_data(sfp, 226, 2);
slen += strtox(outbuf + slen,"\",\"sfp_txbias\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_txbias\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 228, 2); sfp_send_data(sfp, 228, 2);
slen += strtox(outbuf + slen,"\",\"sfp_txpower\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_txpower\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 230, 2); sfp_send_data(sfp, 230, 2);
slen += strtox(outbuf + slen,"\",\"sfp_rxpower\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_rxpower\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 232, 2); sfp_send_data(sfp, 232, 2);
if (sfp_options[machine.is_sfp[i]-1] & 0x10) { if (sfp_options[sfp] & 0x10) {
slen += strtox(outbuf + slen,"\",\"sfp_temp_cal\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_temp_cal\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 212, 4); sfp_send_data(sfp, 212, 4);
slen += strtox(outbuf + slen,"\",\"sfp_vcc_cal\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_vcc_cal\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 216, 4); sfp_send_data(sfp, 216, 4);
slen += strtox(outbuf + slen,"\",\"sfp_txbias_cal\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_txbias_cal\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 204, 4); sfp_send_data(sfp, 204, 4);
slen += strtox(outbuf + slen,"\",\"sfp_txpower_cal\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_txpower_cal\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 208, 4); sfp_send_data(sfp, 208, 4);
slen += strtox(outbuf + slen,"\",\"sfp_rxpower_cal\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_rxpower_cal\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 184, 16); sfp_send_data(sfp, 184, 16);
sfp_send_data(machine.is_sfp[i] - 1, 200, 4); sfp_send_data(sfp, 200, 4);
} }
slen += strtox(outbuf + slen,"\",\"sfp_state\":\"0x"); slen += strtox(outbuf + slen,"\",\"sfp_state\":\"0x");
sfp_send_data(machine.is_sfp[i] - 1, 238, 1); sfp_send_data(sfp, 238, 1);
} }
slen += strtox(outbuf + slen,"\",\"sfp_vendor\":\""); slen += strtox(outbuf + slen,"\",\"sfp_vendor\":\"");
for (register uint8_t s = 0; s < 16; s++) for (register uint8_t s = 0; s < 16 && sfp_module_vendor[sfp][s]; s++)
outbuf[slen++] = sfp_module_vendor[machine.is_sfp[i]-1][s]; outbuf[slen++] = sfp_module_vendor[sfp][s];
slen += strtox(outbuf + slen,"\",\"sfp_model\":\""); slen += strtox(outbuf + slen,"\",\"sfp_model\":\"");
for (register uint8_t s = 0; s < 16; s++) for (register uint8_t s = 0; s < 16 && sfp_module_model[sfp][s]; s++)
outbuf[slen++] = sfp_module_model[machine.is_sfp[i]-1][s]; outbuf[slen++] = sfp_module_model[sfp][s];
slen += strtox(outbuf + slen,"\",\"sfp_serial\":\""); slen += strtox(outbuf + slen,"\",\"sfp_serial\":\"");
for (register uint8_t s = 0; s < 16; s++) for (register uint8_t s = 0; s < 16 && sfp_module_serial[sfp][s]; s++)
outbuf[slen++] = sfp_module_serial[machine.is_sfp[i]-1][s]; outbuf[slen++] = sfp_module_serial[sfp][s];
slen += strtox(outbuf + slen,"\",\"sfp_los\":"); slen += strtox(outbuf + slen,"\",\"sfp_los\":");
if (machine.sfp_port[machine.is_sfp[i]-1].pin_los == GPIO_NA) { if (machine.sfp_port[sfp].pin_los == GPIO_NA) {
slen += strtox(outbuf + slen,"null"); slen += strtox(outbuf + slen,"null");
} else { } else {
bool_to_html(sfp_pins_last & (0x2 << (((machine.is_sfp[i]-1) << 2)))); bool_to_html(sfp_pins_last & (0x2 << (sfp << 2)));
} }
} else { } else {
bool_to_html(0); bool_to_html(0);
@@ -850,7 +858,9 @@ void send_vlanlist(void)
uint8_t first = 1; uint8_t first = 1;
slen = strtox(outbuf, HTTP_RESPONCE_JSON); slen = strtox(outbuf, HTTP_RESPONCE_JSON);
char_to_html('['); slen += strtox(outbuf + slen, "{\"mgmt\":");
itoa16_html(management_vlan);
slen += strtox(outbuf + slen, ",\"vlan\":[");
for (i = 1; i < 4095; i++) { for (i = 1; i < 4095; i++) {
if (vlan_get(i) < 0) if (vlan_get(i) < 0)
@@ -858,7 +868,7 @@ void send_vlanlist(void)
if (!(sfr_data[0] & 0x02)) /* bit 1: VLAN table entry valid */ if (!(sfr_data[0] & 0x02)) /* bit 1: VLAN table entry valid */
continue; continue;
if (slen + 139 > TCP_OUTBUF_SIZE) /* 138 bytes worst-case entry + 1 byte for closing ']' */ if (slen + 141 > TCP_OUTBUF_SIZE) /* comma + 138-byte worst-case entry + closing "]}" */
break; break;
if (!first) if (!first)
@@ -880,4 +890,5 @@ void send_vlanlist(void)
} }
char_to_html(']'); char_to_html(']');
char_to_html('}');
} }
+196 -1
View File
@@ -344,7 +344,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_tx_disable = GPIO_NA, .sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1, .sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 }, .sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.reset_pin = GPIO_NA, .reset_pin = GPIO48_I2C_SCL1,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 }, .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}, .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
.led_sets = { .led_sets = {
@@ -415,6 +415,7 @@ void machine_custom_init(void) { }
__code const struct machine machine = { __code const struct machine machine = {
.machine_name = "SWTGW218AS 8+1 Managed Switch", .machine_name = "SWTGW218AS 8+1 Managed Switch",
.isRTL8373 = 1, .isRTL8373 = 1,
.mac_flash_offset = 0x1FC000,
.min_port = 0, .min_port = 0,
.max_port = 8, .max_port = 8,
.n_sfp = 1, .n_sfp = 1,
@@ -896,6 +897,130 @@ void machine_custom_init(void)
reg_bit_clear(RTL837X_REG_LED_MODE, 7); reg_bit_clear(RTL837X_REG_LED_MODE, 7);
} }
#elif defined MACHINE_SWTG024AS_A_2_0_1_5C_1SFP
__code const struct machine machine = {
.machine_name = "SWTG024AS-A-V2.0.1-5C-1SFP",
.isRTL8373 = 0,
.min_port = 3,
.max_port = 8,
.n_sfp = 1,
.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, 1},
.sfp_port[0].pin_detect = GPIO38,
.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 },
.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, 0, 0, 0, 0, 0, 1},
/* Ports 1-5 RJ45 use set 0, port 9 SFP uses set 1
* Ports 1-5: Green: 2.5GBit, Amber: 10/100/1000MBit
* SFP-port: Blue: 10GBit, Green: 100MBit-2.5GBit
*/
.led_sets = {
{
LEDS_2G5 | LEDS_LINK | LEDS_ACT,
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
LEDS_DUPLEX,
LEDS_2G5 | LEDS_LINK | LEDS_ACT
},
{
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT,
LEDS_10G | LEDS_LINK | LEDS_ACT,
LEDS_2G5 | LEDS_LINK,
LEDS_COL | LEDS_DUPLEX
},
},
.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
},
};
void machine_custom_init(void)
{
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
__code const struct machine machine = {
.machine_name = "SWTG024AS-V2.0",
.isRTL8373 = 0,
.min_port = 3,
.max_port = 8,
.n_sfp = 1,
.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, 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 = { .sda = GPIO39_I2C_SDA4, .scl = 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, 0, 0, 0, 0, 0, 1},
/* Ports 1-5 RJ45 use set 0, port 9 SFP uses set 1
* Ports 1-5: Green: 2.5GBit, Amber: 10/100/1000MBit
* SFP-port: Blue: 10GBit, Amber: 100MBit-2.5GBit
*/
.led_sets = {
{
LEDS_2G5 | LEDS_LINK | LEDS_ACT,
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
LEDS_DUPLEX,
LEDS_2G5 | LEDS_LINK | LEDS_ACT
},
{
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT,
LEDS_10G | LEDS_LINK | LEDS_ACT,
LEDS_2G5 | LEDS_LINK,
LEDS_COL | LEDS_DUPLEX
},
},
.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
},
};
void machine_custom_init(void)
{
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_ZX310S_4T2XT #elif defined MACHINE_ZX310S_4T2XT
__code const struct machine machine = { __code const struct machine machine = {
.machine_name = "ZX310S_4T2XT", .machine_name = "ZX310S_4T2XT",
@@ -939,6 +1064,76 @@ void machine_custom_init(void) {
REG_SET(RTL837X_PIN_MUX_0, 0x30db68bf); REG_SET(RTL837X_PIN_MUX_0, 0x30db68bf);
} }
#elif defined MACHINE_FG_4GT_2SX_V2_0
__code const struct machine machine = {
.machine_name = "FG-4GT-2SX_V2.0",
.isRTL8373 = 0,
.min_port = 3,
.max_port = 8,
.n_sfp = 2,
.log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5},
.phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0},
.is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1},
// Left SFP port
.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 },
// 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 },
.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},
/* Ports 1-4 RJ45 use set 0, port 5-6 SFP uses set 1
* Ports 1-4: Green: 2.5GBit, Amber: 10/100/1000MBit
* Ports 5-6: Green: 100MBit-10GBit
*/
.led_sets = {
{
LEDS_2G5 | LEDS_LINK | LEDS_ACT,
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
0,
LEDS_2G5 | LEDS_LINK | LEDS_ACT
},
{
LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
LEDS_10G | LEDS_LINK,
0,
LEDS_COL | LEDS_DUPLEX
},
{
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
LEDS_2G5 | LEDS_1G | LEDS_LINK,
LEDS_5G | LEDS_2G5 | LEDS_LINK | LEDS_ACT,
LEDS_10G | LEDS_LINK | LEDS_ACT
},
{
LEDS_TX,
LEDS_RX,
LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5 |
LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_ACT,
LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5 |
LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M | LEDS_LINK
},
},
.led_mux_custom = 1,
.led_mux = {
0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x14, 0x3f, 0x15, 0x16,
0x18, 0x0e, 0x19, 0x11, 0x12, 0x1a, 0x15, 0x16, 0x1c, 0x19,
0x1a, 0x1d, 0x1d, 0x1e, 0x1e, 0x20, 0x21, 0x22
},
};
void machine_custom_init(void) {
REG_SET(RTL837X_REG_LED_GLB_IO_EN, 0x7624155b);
}
#else #else
#error "Please select a machine type in machine.h" #error "Please select a machine type in machine.h"
#endif #endif
+4
View File
@@ -28,6 +28,9 @@
// #define MACHINE_HI_K0801WS // #define MACHINE_HI_K0801WS
// #define MACHINE_FNS1200P // #define MACHINE_FNS1200P
// #define MACHINE_PCB_SWTG024AS_A_2_0_1 // #define MACHINE_PCB_SWTG024AS_A_2_0_1
// #define MACHINE_SWTG024AS_A_2_0_1_5C_1SFP
// #define MACHINE_SWTG024AS_V2_0
// #define MACHINE_FG_4GT_2SX_V2_0
typedef struct { typedef struct {
// GPIO pins for SDA/SCL // GPIO pins for SDA/SCL
@@ -82,6 +85,7 @@ typedef struct machine {
uint32_t led_sets[4][4]; uint32_t led_sets[4][4];
uint8_t led_mux_custom; uint8_t led_mux_custom;
uint8_t led_mux[28]; uint8_t led_mux[28];
uint32_t mac_flash_offset;
}; };
typedef struct machine_runtime typedef struct machine_runtime
+6
View File
@@ -115,6 +115,11 @@ struct flash_region_t {
extern __xdata char port_names[9][PORT_NAME_SIZE]; extern __xdata char port_names[9][PORT_NAME_SIZE];
/* System hostname (device identity). Set via `hostname <text>` and the System
* Settings page, reported in /information.json. Other modules (e.g. LLDP, which
* advertises it as the System Name TLV) read it from here. */
extern __xdata char hostname[24];
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2]; extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2];
extern __xdata struct uip_eth_addr uip_ethaddr; extern __xdata struct uip_eth_addr uip_ethaddr;
@@ -158,6 +163,7 @@ uint16_t strlen(register __code const char *s);
uint16_t strlen_x(register __xdata const char *s); uint16_t strlen_x(register __xdata const char *s);
uint16_t strtox(register __xdata uint8_t *dst, register __code const char *s); uint16_t strtox(register __xdata uint8_t *dst, register __code const char *s);
uint16_t strcpy(register __xdata uint8_t *dst, register const char *s); uint16_t strcpy(register __xdata uint8_t *dst, register const char *s);
char strcmp(register __xdata const uint8_t *a, register __code const uint8_t *b);
void tcpip_output(void); void tcpip_output(void);
uint8_t read_flash(uint8_t bank, __code uint8_t *addr); uint8_t read_flash(uint8_t bank, __code uint8_t *addr);
void get_random_32(void); void get_random_32(void);
+2 -7
View File
@@ -96,11 +96,6 @@ void igmp_setup(void) __banked
// Enable lookup of IPv4 MC addresses in table // Enable lookup of IPv4 MC addresses in table
reg_bit_set(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH); reg_bit_set(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH);
// Configure per-port IGMP configuration, bits 0-10 enable MC protocol snooping,
// bits 16-24 configure max MC group used by that port. For now all protocols are flooded (01)
for (i = machine.min_port; i <= machine.max_port; i++)
REG_SET(RTL837X_IGMP_PORT_CFG + (i << 2), 0x00ff7c15);
/* Configure per-port IGMP operations when protocol messages are received /* Configure per-port IGMP operations when protocol messages are received
* bits 0-9 enable MC protocol snooping * bits 0-9 enable MC protocol snooping
* bit 10: Enable dynamic router port learning * bit 10: Enable dynamic router port learning
@@ -223,7 +218,7 @@ void igmp_packet_handler(void) __banked
#endif #endif
#ifdef IPMC_USES_L3MC #ifdef IPMC_USES_L3MC
memset(&entry, 0, sizeof(struct ipmc_table_entry)); memset((__xdata uint8_t *)&entry, 0, sizeof(struct ipmc_table_entry));
// For IPv4 MC, the Source-IP is 0.0.0.0 // For IPv4 MC, the Source-IP is 0.0.0.0
entry.sip[0] = 0x00; entry.sip[1] = 0x00; entry.sip[2] = 0x00; entry.sip[3] = 0x00; entry.sip[0] = 0x00; entry.sip[1] = 0x00; entry.sip[2] = 0x00; entry.sip[3] = 0x00;
// For IPv4 MC, the Destination-IP is the IPv4 MC address // For IPv4 MC, the Destination-IP is the IPv4 MC address
@@ -235,7 +230,7 @@ void igmp_packet_handler(void) __banked
* yy = MC_IP[2] * yy = MC_IP[2]
* zz = MC_IP[3] * zz = MC_IP[3]
*/ */
memset(&entry, 0, sizeof(struct l2mc_table_entry)); memset((__xdata uint8_t *)&entry, 0, sizeof(struct l2mc_table_entry));
entry.mac[0] = 0x01; entry.mac[1] = 0x00; entry.mac[2] = 0x5e; entry.mac[0] = 0x01; entry.mac[1] = 0x00; entry.mac[2] = 0x5e;
entry.mac[3] = IGMP_I->mc_ip[1] & 0x7f; entry.mac[4] = IGMP_I->mc_ip[2]; entry.mac[5] = IGMP_I->mc_ip[3]; entry.mac[3] = IGMP_I->mc_ip[1] & 0x7f; entry.mac[4] = IGMP_I->mc_ip[2]; entry.mac[5] = IGMP_I->mc_ip[3];
entry.vlan = 1; //TODO: Get this out of the packet and compare with VLAN table! entry.vlan = 1; //TODO: Get this out of the packet and compare with VLAN table!
+8
View File
@@ -115,6 +115,9 @@ uint16_t port_pvid_get(uint8_t port) __banked
void vlan_delete(uint16_t vlan) __banked void vlan_delete(uint16_t vlan) __banked
{ {
if (!vlan || vlan >= 0xfff)
return;
print_string("\nvlan_delete called \n"); print_short(vlan); print_string("\nvlan_delete called \n"); print_short(vlan);
vlan_name_remove(vlan); vlan_name_remove(vlan);
REG_WRITE(RTL837x_TBL_DATA_IN_A, 0, 0, 0, 0); REG_WRITE(RTL837x_TBL_DATA_IN_A, 0, 0, 0, 0);
@@ -197,6 +200,11 @@ __xdata uint16_t vlan_name(register uint16_t vlan) __banked
*/ */
void vlan_create(void) __banked void vlan_create(void) __banked
{ {
if (!vlan_settings.vlan || vlan_settings.vlan >= 0xfff) {
print_string("\nInvalid VLAN: "); print_short(vlan_settings.vlan); write_char('\n');
return;
}
// For now, the CPU-port is always a tagged member: // For now, the CPU-port is always a tagged member:
vlan_settings.members |= 0x0200; // Set 10th bit vlan_settings.members |= 0x0200; // Set 10th bit
vlan_settings.tagged |= 0x0200; vlan_settings.tagged |= 0x0200;
+16 -8
View File
@@ -305,32 +305,40 @@
#ifdef REGDBG #ifdef REGDBG
#define REG_SET(r, v) SFR_DATA_24 = (((uint32_t)v) >> 24) & 0xff; \ #define REG_SET(r, v) do { \
SFR_DATA_24 = (((uint32_t)v) >> 24) & 0xff; \
SFR_DATA_16 = (((uint32_t)v) >> 16) & 0xff; \ SFR_DATA_16 = (((uint32_t)v) >> 16) & 0xff; \
SFR_DATA_8 = (((uint16_t)v) >> 8 & 0xff); \ SFR_DATA_8 = (((uint16_t)v) >> 8 & 0xff); \
SFR_DATA_0 = (v) & 0xff; \ SFR_DATA_0 = (v) & 0xff; \
reg_write(r); \ reg_write(r); \
write_char('R'); print_byte(r >> 8); print_byte(r); write_char('-'); \ write_char('R'); print_byte(r >> 8); print_byte(r); write_char('-'); \
print_byte(((v) >> 24) & 0xff); print_byte((v) >> 16 & 0xff); print_byte((v) >> 8 & 0xff); print_byte( (v) & 0xff); write_char(' '); print_byte(((v) >> 24) & 0xff); print_byte((v) >> 16 & 0xff); print_byte((v) >> 8 & 0xff); print_byte( (v) & 0xff); write_char(' '); \
} while (0)
#define REG_WRITE(r, v24, v16, v8, v0) SFR_DATA_24 = (v24); \ #define REG_WRITE(r, v24, v16, v8, v0) do { \
SFR_DATA_24 = (v24); \
SFR_DATA_16 = (v16); \ SFR_DATA_16 = (v16); \
SFR_DATA_8 = (v8); \ SFR_DATA_8 = (v8); \
SFR_DATA_0 = (v0); \ SFR_DATA_0 = (v0); \
reg_write(r); \ reg_write(r); \
write_char('R'); print_byte(r>>8); print_byte(r); write_char('-'); print_byte(v24); print_byte(v16); print_byte(v8); print_byte(v0); write_char(' '); write_char('R'); print_byte(r>>8); print_byte(r); write_char('-'); print_byte(v24); print_byte(v16); print_byte(v8); print_byte(v0); write_char(' '); \
} while (0)
#else #else
#define REG_SET(r, v) SFR_DATA_24 = (((uint32_t)v) >> 24) & 0xff; \ #define REG_SET(r, v) do { \
SFR_DATA_24 = (((uint32_t)v) >> 24) & 0xff; \
SFR_DATA_16 = (((uint32_t)v) >> 16) & 0xff; \ SFR_DATA_16 = (((uint32_t)v) >> 16) & 0xff; \
SFR_DATA_8 = (((uint16_t)v) >> 8 & 0xff); \ SFR_DATA_8 = (((uint16_t)v) >> 8 & 0xff); \
SFR_DATA_0 = (v) & 0xff; \ SFR_DATA_0 = (v) & 0xff; \
reg_write(r); reg_write(r); \
} while (0)
#define REG_WRITE(r, v24, v16, v8, v0) SFR_DATA_24 = (v24); \ #define REG_WRITE(r, v24, v16, v8, v0) do { \
SFR_DATA_24 = (v24); \
SFR_DATA_16 = (v16); \ SFR_DATA_16 = (v16); \
SFR_DATA_8 = (v8); \ SFR_DATA_8 = (v8); \
SFR_DATA_0 = (v0); \ SFR_DATA_0 = (v0); \
reg_write(r); reg_write(r); \
} while (0)
#endif #endif
#endif #endif
+115 -16
View File
@@ -25,6 +25,7 @@
#include "machine.h" #include "machine.h"
#include "phy.h" #include "phy.h"
#include "syslog.h" #include "syslog.h"
#include "httpd/page_impl.h"
extern __code const struct machine machine; extern __code const struct machine machine;
extern __xdata uint32_t flash_size; extern __xdata uint32_t flash_size;
@@ -120,6 +121,7 @@ __xdata uint16_t management_vlan;
__xdata uint8_t tx_seq; __xdata uint8_t tx_seq;
__xdata uint8_t stpEnabled; __xdata uint8_t stpEnabled;
__xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */
__code uint16_t bit_mask[16] = { __code uint16_t bit_mask[16] = {
0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
@@ -138,10 +140,25 @@ __xdata char sfp_module_model[2][17];
__xdata char sfp_module_serial[2][17]; __xdata char sfp_module_serial[2][17];
__xdata uint8_t sfp_options[2]; __xdata uint8_t sfp_options[2];
__xdata uint8_t sfp_speed[2]; __xdata uint8_t sfp_speed[2];
__xdata uint8_t sfp_quirks[2];
__xdata bool button_last; __xdata bool button_last;
__xdata uint8_t button_sec_counter_last; __xdata uint8_t button_sec_counter_last;
volatile __bit tx_buf_empty; volatile __bit tx_buf_empty;
__code enum sfp_quirk {
SFP_QUIRK_DDM = (1 << 0),
};
struct sfp_quirk_entry {
__code char *vendor; // Set vendor or model to 0 to act as wildcard
__code char *model;
uint8_t quirks;
};
static __code struct sfp_quirk_entry sfp_quirk_table[] = {
{ "QSFPTEK", "QT-SFP+-T", SFP_QUIRK_DDM },
};
struct eth_in { struct eth_in {
struct uip_eth_addr dst; struct uip_eth_addr dst;
struct uip_eth_addr src; struct uip_eth_addr src;
@@ -327,6 +344,21 @@ uint16_t strlen_x(register __xdata const char *s)
} }
char strcmp(register __xdata const uint8_t *a, register __code const uint8_t *b)
{
uint8_t i = 0;
while (b[i] && (b[i] == a[i]))
i++;
if (a[i] < b[i])
return -1;
else if (a[i] > b[i])
return 1;
return 0;
}
void print_short(uint16_t a) void print_short(uint16_t a)
{ {
// allocating the registers first improves the sdcc code here // allocating the registers first improves the sdcc code here
@@ -1180,7 +1212,7 @@ static inline uint8_t sfp_rate_to_sds_config(register uint8_t rate)
return SDS_1000BX_FIBER; return SDS_1000BX_FIBER;
if (rate >= 0x19 && rate <= 0x20) // Ethernet 2.5 GBit if (rate >= 0x19 && rate <= 0x20) // Ethernet 2.5 GBit
return SDS_HSG; return SDS_HSG;
if (rate >= 0x63 && rate < 0x70) if (rate >= 0x62 && rate < 0x70)
return SDS_10GR; return SDS_10GR;
return 0xff; return 0xff;
} }
@@ -1199,18 +1231,46 @@ void sfp_print_info(uint8_t sfp)
print_string("\n"); print_string("\n");
} }
// Normalize strings from EEPROM by removing any trailing spaces; this allows simpler comparisons
void sfp_read_field(__xdata char *dst, uint8_t sfp, uint8_t start, uint8_t length) __reentrant
{
dst[length] = '\0';
for (uint8_t i = 0; i < length; i++)
dst[i] = sfp_read_reg(sfp, start + i);
while (length > 0 && dst[--length] == ' ')
dst[length] = '\0';
}
void sfp_get_info(uint8_t sfp) void sfp_get_info(uint8_t sfp)
{ {
for (uint8_t i = 20; i < 36; i++) sfp_read_field(sfp_module_vendor[sfp], sfp, 20, 16);
sfp_module_vendor[sfp][i-20] = sfp_read_reg(sfp, i); sfp_read_field(sfp_module_model[sfp], sfp, 40, 16);
sfp_module_vendor[sfp][16] = '\0'; sfp_read_field(sfp_module_serial[sfp], sfp, 68, 16);
for (uint8_t i = 40; i < 56; i++) }
sfp_module_model[sfp][i-40] = sfp_read_reg(sfp, i);
sfp_module_model[sfp][16] = '\0'; void sfp_apply_quirks(uint8_t sfp) __reentrant
for (uint8_t i = 68; i < 84; i++) {
sfp_module_serial[sfp][i-68] = sfp_read_reg(sfp, i); sfp_quirks[sfp] = 0;
sfp_module_serial[sfp][16] = '\0';
for (uint8_t i = 0; i < sizeof(sfp_quirk_table) / sizeof(*sfp_quirk_table); i++) {
if (!sfp_quirk_table[i].vendor || !strcmp(sfp_module_vendor[sfp], sfp_quirk_table[i].vendor)) {
if (!sfp_quirk_table[i].model || !strcmp(sfp_module_model[sfp], sfp_quirk_table[i].model)) {
sfp_quirks[sfp] |= sfp_quirk_table[i].quirks;
}
}
}
if (sfp_quirks[sfp] & SFP_QUIRK_DDM) {
if (!(sfp_options[sfp] & 0x40)) {
// The module reports that DDM is not implemented, but try a dummy read to confirm
// 0xff would mean a failed I2C read or an impossible (per spec) voltage greater than 6.5V
if (sfp_read_reg(sfp, 226) != 0xff) {
sfp_options[sfp] |= 0x40;
}
}
}
} }
@@ -1255,6 +1315,7 @@ void handle_sfp(void)
print_string("\n"); print_string("\n");
sfp_options[sfp] = sfp_read_reg(sfp, 92); sfp_options[sfp] = sfp_read_reg(sfp, 92);
sfp_get_info(sfp); sfp_get_info(sfp);
sfp_apply_quirks(sfp);
sds_config(machine.sfp_port[sfp].sds, sfp_rate_to_sds_config(rate)); sds_config(machine.sfp_port[sfp].sds, sfp_rate_to_sds_config(rate));
} }
} else { } else {
@@ -1945,6 +2006,34 @@ void check_and_flash_update_image(void)
} }
} }
/* 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)
{
if (hostname[0] != '\0')
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] = '\0';
}
void main(void) void main(void)
{ {
ticks = 0; ticks = 0;
@@ -2023,20 +2112,28 @@ void main(void)
uip_ipaddr(&uip_hostaddr, ownIP[0], ownIP[1], ownIP[2], ownIP[3]); uip_ipaddr(&uip_hostaddr, ownIP[0], ownIP[1], ownIP[2], ownIP[3]);
uip_ipaddr(&uip_draddr, gatewayIP[0], gatewayIP[1], gatewayIP[2], gatewayIP[3]); uip_ipaddr(&uip_draddr, gatewayIP[0], gatewayIP[1], gatewayIP[2], gatewayIP[3]);
uip_ipaddr(&uip_netmask, netmask[0], netmask[1], netmask[2], netmask[3]); uip_ipaddr(&uip_netmask, netmask[0], netmask[1], netmask[2], netmask[3]);
uip_ethaddr.addr[0] = 0xff;
if (machine.mac_flash_offset) {
flash_region.addr = machine.mac_flash_offset;
flash_region.len = FLASH_BUF_SIZE;
flash_read_bulk(flash_buf);
// accept only a real unicast, globally-administered address (reject blank/LAA/multicast/all-zero OUI)
if (flash_buf[0] != 0xff && !(flash_buf[0] & 0x03) && (flash_buf[0] | flash_buf[1] | flash_buf[2])) {
uip_ethaddr.addr[0] = flash_buf[0]; uip_ethaddr.addr[1] = flash_buf[1];
uip_ethaddr.addr[2] = flash_buf[2]; uip_ethaddr.addr[3] = flash_buf[3];
uip_ethaddr.addr[4] = flash_buf[4]; uip_ethaddr.addr[5] = flash_buf[5];
}
}
if (uip_ethaddr.addr[0] == 0xff) { // no valid flash MAC -> generate locally-administered
reg_read_m(RTL837X_REG_CHIP_UUID); reg_read_m(RTL837X_REG_CHIP_UUID);
#ifdef DEBUG
print_string("SoC UUID: "); print_sfr_data();
#endif
uip_ethaddr.addr[0] = 0x06; // LAA prefix uip_ethaddr.addr[0] = 0x06; // LAA prefix
uip_ethaddr.addr[3] = sfr_data[0] ^ sfr_data[3]; uip_ethaddr.addr[3] = sfr_data[0] ^ sfr_data[3];
uip_ethaddr.addr[4] = sfr_data[1] ^ sfr_data[3]; uip_ethaddr.addr[4] = sfr_data[1] ^ sfr_data[3];
uip_ethaddr.addr[5] = sfr_data[2] ^ sfr_data[3]; uip_ethaddr.addr[5] = sfr_data[2] ^ sfr_data[3];
reg_read_m(RTL837X_REG_CHIP_LOT_NO); reg_read_m(RTL837X_REG_CHIP_LOT_NO);
#ifdef DEBUG
print_string(", LOT: "); print_sfr_data(); write_char(' ');
#endif
uip_ethaddr.addr[1] = sfr_data[0] ^ sfr_data[2]; uip_ethaddr.addr[1] = sfr_data[0] ^ sfr_data[2];
uip_ethaddr.addr[2] = sfr_data[1] ^ sfr_data[3]; uip_ethaddr.addr[2] = sfr_data[1] ^ sfr_data[3];
}
print_string("Setting MAC to: "); print_string("Setting MAC to: ");
print_byte(uip_ethaddr.addr[0]); write_char(':'); print_byte(uip_ethaddr.addr[1]); write_char(':'); print_byte(uip_ethaddr.addr[0]); write_char(':'); print_byte(uip_ethaddr.addr[1]); write_char(':');
print_byte(uip_ethaddr.addr[2]); write_char(':'); print_byte(uip_ethaddr.addr[3]); write_char(':'); print_byte(uip_ethaddr.addr[2]); write_char(':'); print_byte(uip_ethaddr.addr[3]); write_char(':');
@@ -2109,6 +2206,8 @@ void main(void)
early_boot_handle_button(); early_boot_handle_button();
execute_config(); execute_config();
/* After the config: a name from it wins, otherwise derive one. */
set_hostname_default();
print_cmd_prompt(); print_cmd_prompt();
idle_ready = 1; idle_ready = 1;
+12 -3
View File
@@ -500,6 +500,10 @@ struct Server serverConstructor(int port, void (*launch)(struct Server *server))
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
int reuse = 1;
if (setsockopt(server.socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
perror("setsockopt(SO_REUSEADDR) failed");
if (bind(server.socket, (struct sockaddr*)&server.address, sizeof(server.address)) < 0) { if (bind(server.socket, (struct sockaddr*)&server.address, sizeof(server.address)) < 0) {
perror("Failed to bind socket...\n"); perror("Failed to bind socket...\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@@ -554,8 +558,12 @@ char *scan_header(char *p)
break; break;
if (is_word(p, "\nContent-Type:")) if (is_word(p, "\nContent-Type:"))
content_type = p + 15; content_type = p + 15;
else if (is_word(p, "\nCookie:")) else if (is_word(p, "\nCookie:")) {
session = p + 17; session = p + 9;
while (*session == ' ') session++;
const char *s = strstr(session, "session=");
if (s) session = s + 8;
}
} }
if (content_type && is_word(content_type, "multipart/form-data; boundary")) { if (content_type && is_word(content_type, "multipart/form-data; boundary")) {
printf("Found multiplart\n"); printf("Found multiplart\n");
@@ -799,7 +807,8 @@ void launch(struct Server *server)
printf("Password accepted!\n"); printf("Password accepted!\n");
response = "HTTP/1.1 302 Found\r\n" response = "HTTP/1.1 302 Found\r\n"
"Location: index.html\r\n" "Location: index.html\r\n"
"Set-Cookie: session=" SESSION_ID "; SameSite=Strict\r\n"; "Set-Cookie: session=" SESSION_ID "; Path=/; SameSite=Strict\r\n"
"\r\n";
} else { } else {
response = "HTTP/1.1 302 Found\r\n" response = "HTTP/1.1 302 Found\r\n"
"Location: login.html\r\n\r\n"; "Location: login.html\r\n\r\n";
+149
View File
@@ -0,0 +1,149 @@
# LED GPIO Utilities
This directory contains utility talking to RTL837x switch IC via I2C bus.
These scripts are designed to help with monitoring, debugging GPIO, and identifying LED configurations.
(Only tested in Linux)
## Hardware requirements
To use these utilities, you need:
- A hardware dongle that can communicate with I2C devices. One example is the [I2C-Pico-USB](https://github.com/dquadros/I2C-Pico-USB) which provides USB-to-I2C connectivity.
- Connection between the hardware dongle and the RTL837x's I2C communication port.
## Scripts
### 1. `i2c_read_rtl_gpio.py`
This script reads RTL GPIO register values via I2C and displays changes in GPIO states.
**Features:**
- Reads live RTL GPIO register values via I2C (default address 0x5C)
- Monitors GPIO changes with delta detection
- Allows specification of I2C bus, sleep interval, and ignored GPIO pins
- Shows changes in real-time with GPIO index display
**Usage:**
```bash
# Basic usage (defaults to I2C bus 1, 2s sleep interval)
python3 i2c_read_rtl_gpio.py
# Specify I2C bus
python3 i2c_read_rtl_gpio.py --i2c-bus 0
# Specify sleep interval in seconds
python3 i2c_read_rtl_gpio.py --sleep-interval 5
# Ignore specific GPIO pins
python3 i2c_read_rtl_gpio.py --ignored-ios 28 31 34 44
# Combine options
python3 i2c_read_rtl_gpio.py --i2c-bus 2 --sleep-interval 1 --ignored-ios 28 31
```
**Output Format:**
- Displays register address and data in hex format
- Shows GPIO pins that have changed since last read
- Example: `0044: 00 00 00 00 00 00 00 00`
### 2. `i2c_dump_rtl_regs.py`
This script dumps all register values from an RTL device via I2C.
**Features:**
- Dumps registers sequentially from address 0x0000 to 0xFFFF
- Reads 16 bytes at a time for efficiency
- Configurable I2C bus number
- Provides complete register dump in hex format
**Usage:**
```bash
# Basic usage (defaults to I2C bus 1)
python3 i2c_dump_rtl_regs.py >reg_dump.txt
# Specify I2C bus
python3 i2c_dump_rtl_regs.py --bus 0 >reg_dump.txt
# Or using short option
python3 i2c_dump_rtl_regs.py -b 2 >reg_dump.txt
```
**Output Format:**
- Displays address and 16 bytes of data in hex format
- Example: `0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00`
### 3. `dec_leds_from_dump.py`
This script decodes LED configuration from a register dump file ( the output from `i2c_dump_rtl_regs.py`).
**Features:**
- Parses register dump files (reg_dump.txt format)
- Decodes LED pad configurations and LED sets
- Maps LED types to bit positions based on configuration
- Outputs LED mux configuration and set mappings
**Usage:**
```bash
# Must be run in same directory as reg_dump.txt
python3 dec_leds_from_dump.py
```
**Output Format:**
- LED pad configuration (hex values for each pad)
- LED set configurations with descriptions of LED types
- Port selection mappings
**Requirements:**
- Requires a `reg_dump.txt` file containing the register dump output
## Requirements
All scripts require:
- Python 3
- `smbus2` Python package
(Depend upon Linux distribution) Install with:
```bash
apt install python3-smbus2
```
OR
```bash
pip3 install smbus2
```
## Common Use Cases
Both `i2c_read_rtl_gpio.py` and `i2c_dump_rtl_regs.py` shall run with the **original** firmware, not RTLPlayground firmware.
### Monitoring GPIO Changes
```bash
# Monitor GPIO changes on bus 1 with 1-second intervals
python3 i2c_read_rtl_gpio.py --i2c-bus 1 --sleep-interval 1
```
### Register Analysis
```bash
# Dump all device registers
python3 i2c_dump_rtl_regs.py --bus 2 > reg_dump.txt
# Analyze the register dump to understand LED configuration
python3 dec_leds_from_dump.py
```
## Configuration
All scripts support command-line arguments for flexible configuration:
- `--i2c-bus` or `-b`: Specify I2C bus (default: 1)
- `--sleep-interval` or `-s`: Sleep between reads in seconds (default: 2)
- `--ignored-ios` or `-i`: GPIO pins to ignore (default: [28, 31, 34, 44])
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
regs = list(range(65536))
glb_mux = 0x65E0
led3_0_set1 = 0x6528
led1_0_set0 = 0x6548
led_port_sel = 0x654C
def get_word(i):
val = (regs[i + 3] << 24) + (regs[i + 2] << 16) + (regs[i + 1] << 8) + regs[i]
# print(f"READING {i:04x} : {val:08x}")
return val
with open("reg_dump.txt", "r") as file:
for line in file:
addr, data = line.split(":")
data = [int(x, 16) for x in data.strip().split(" ")]
addr = int(addr, 16)
for x in range(len(data)):
regs[addr + x] = data[x]
ledstr = []
ledfmt = []
print("LED pad Configuration:")
for i in range(28):
j = i % 5
if j == 0:
idx = glb_mux + (i // 5) * 4
val = get_word(idx)
ledval = (val >> (6 * j)) & 0x3F
ledstr.append(f"{ledval:02x}")
ledfmt.append(f"{i:02x}")
print(f"{' '.join(ledfmt)}")
print(f"{' '.join(ledstr)}")
print(f".led_mux = {{ 0x{', 0x'.join(ledstr)} }},")
LED_TYPES = [
" 10G",
16,
" TWO_5G",
17,
" 5G",
18,
" TWO_2G5",
19,
" 2G5",
0,
" TWO_1G",
1,
" 1G",
2,
" 500M",
3,
" 100M",
4,
" 10M",
5,
" LINK",
6,
" LINK_FLASH",
7,
" ACT",
8,
" RX",
9,
" TX",
10,
" COL",
11,
" DUPLEX",
12,
" TRAINING",
13,
" MASTER",
14,
"",
15,
]
led_set = []
led_set_str = []
print("\nLED-set Configuration:")
print("LED-ID 0 1 2 3")
for i in range(4):
idval = []
idvalstr = []
for id in range(4):
val = 0xFFFF & (
get_word(led1_0_set0 - 8 * i - ((id >> 1) << 2)) >> (16 * (id & 1))
)
valhi = (
0xF & (get_word(led3_0_set1 - 4 * (i >> 1)) >> (16 * (i & 1) + 4 * id))
) << 16
val += valhi
idval.append(val)
valstr = "("
for bit in range(0, len(LED_TYPES), 2):
if val & (1 << (LED_TYPES[bit + 1])):
valstr += LED_TYPES[bit]
valstr += ")"
idvalstr.append(valstr)
led_set.append(idval)
led_set_str.append(idvalstr)
idstr = " ".join([f"{d:05x}" for d in idval])
print(f"SET {i}: {idstr}")
# print(f"{idvalstr}")
portsel = get_word(led_port_sel)
for i in range(3, 9):
sel = (portsel >> (i << 1)) & 3
print(f"Port {i}: SET {sel}: {', '.join(led_set_str[sel])}")
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
import argparse
from smbus2 import SMBus, i2c_msg
# Configuration
DEVICE_ADDR = 0x5C # Replace with your device address
NUM_BYTES = 16 # Bytes to read
# Parse command line arguments
parser = argparse.ArgumentParser(description="Dump RTL registers via I2C")
parser.add_argument(
"-b",
"--i2c-bus",
type=int,
required=True,
help="I2C bus number (use i2cdetect -l to find out the bus number of the dongle)",
)
args = parser.parse_args()
# Open I2C bus
with SMBus(args.i2c_bus) as bus:
for addr in range(0, 65536, NUM_BYTES):
# Create write message (send register address)
write = i2c_msg.write(DEVICE_ADDR, [addr >> 8, addr & 0xFF])
# Create read message (read 2 bytes)
read = i2c_msg.read(DEVICE_ADDR, NUM_BYTES)
# Perform combined transaction
bus.i2c_rdwr(write, read)
new_data = list(read)
# Convert read message to string
datastr = " ".join([f"{x:02x}" for x in new_data])
print(f"{addr:04x}: {datastr}")
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
import time
import argparse
from smbus2 import SMBus, i2c_msg
# Configuration
I2C_BUS = 1
DEVICE_ADDR = 0x5C # Replace with your device address
NUM_BYTES = 8 # Bytes to read
SLEEP_INTERVAL = 1
# Handle command line arguments for ignored IOs
# default to ignore IOs related to SYS_LED, UART, SMI, SPI
IGNORED_IOS = [28, 31, 32, 34, 35, 42, 43, 44, 45]
# Setup argument parser
parser = argparse.ArgumentParser(description="Read I2C data from RTL GPIO expander")
parser.add_argument(
"-b",
"--i2c-bus",
type=int,
required=True,
help="I2C bus number (use i2cdetect -l to find out the bus number of the dongle)",
)
parser.add_argument(
"-s",
"--sleep-interval",
type=int,
default=SLEEP_INTERVAL,
help=f"Sleep interval in seconds (default: {SLEEP_INTERVAL})",
)
parser.add_argument(
"-i",
"--ignored-ios",
nargs="*",
type=int,
default=IGNORED_IOS,
help=f"List of GPIO pins to ignore (default: {IGNORED_IOS})",
)
args = parser.parse_args()
I2C_BUS = args.i2c_bus
first_read = True
last_data = [0 for x in range(NUM_BYTES)]
# Open I2C bus
with SMBus(I2C_BUS) as bus:
addr = 0x44
while True:
# Create write message (send register address)
write = i2c_msg.write(DEVICE_ADDR, [addr >> 8, addr & 0xFF])
# Create read message (read NUM_BYTES bytes)
read = i2c_msg.read(DEVICE_ADDR, NUM_BYTES)
# Perform combined transaction
bus.i2c_rdwr(write, read)
new_data = list(read)
if first_read:
delta_data = last_data
first_read = False
else:
delta_data = [new_data[x] ^ last_data[x] for x in range(NUM_BYTES)]
last_data = new_data
# Convert read message to list
datastr = " ".join([f"{x:02x}" for x in new_data])
deltastr = ""
chg_list = []
for x in range(NUM_BYTES):
if delta_data[x] == 0:
continue
for y in range(8):
if delta_data[x] & (1 << y):
idx = x * 8 + y
if idx not in args.ignored_ios:
deltastr += f"\n{' ':8s}GPIO{idx}"
# deltastr = " ".join([ f"{x:02x}" for x in delta_data])
print(f"{addr:04x}: {datastr}{deltastr}")
time.sleep(args.sleep_interval)
+15 -1
View File
@@ -106,9 +106,23 @@ typedef unsigned short uip_stats_t;
/** /**
* uIP buffer size. * uIP buffer size.
* *
* Sized to the largest frame the CPU port accepts on ingress: anything above
* that the NIC drops in hardware, so a larger buffer only costs XDATA. Measured
* with ICMP, which bypasses MSS and so probes the hardware directly: a 1502-byte
* payload is answered and 1503 is not, which puts the frame at 1556 bytes of
* uip_buf. The limit is the NIC's rather than a port's, so how the frame was
* tagged on the wire does not change it.
*
* \hideinitializer * \hideinitializer
*/ */
#define UIP_CONF_BUFFER_SIZE 2200 #define UIP_CONF_BUFFER_SIZE 1556
/**
* Bytes of the buffer kept out of the advertised MSS.
*
* \hideinitializer
*/
#define UIP_CONF_BUFFER_EXTRA 30
/** /**
* CPU byte order. * CPU byte order.
+13 -5
View File
@@ -298,11 +298,8 @@
/** /**
* The TCP maximum segment size. * The TCP maximum segment size.
*
* This is should not be to set to more than
* UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN.
*/ */
#define UIP_TCP_MSS (UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN) #define UIP_TCP_MSS (UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN - UIP_BUFFER_EXTRA)
/** /**
* The size of the advertised receiver's window. * The size of the advertised receiver's window.
@@ -381,6 +378,17 @@
#define UIP_BUFSIZE UIP_CONF_BUFFER_SIZE #define UIP_BUFSIZE UIP_CONF_BUFFER_SIZE
#endif /* UIP_CONF_BUFFER_SIZE */ #endif /* UIP_CONF_BUFFER_SIZE */
/**
* Bytes of uip_buf kept out of the advertised MSS.
*
* \hideinitializer
*/
#ifndef UIP_CONF_BUFFER_EXTRA
#define UIP_BUFFER_EXTRA 0
#else /* UIP_CONF_BUFFER_EXTRA */
#define UIP_BUFFER_EXTRA UIP_CONF_BUFFER_EXTRA
#endif /* UIP_CONF_BUFFER_EXTRA */
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2]; extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2];
@@ -447,7 +455,7 @@ void uip_log(char *msg);
#ifdef UIP_CONF_LLH_LEN #ifdef UIP_CONF_LLH_LEN
#define UIP_LLH_LEN UIP_CONF_LLH_LEN #define UIP_LLH_LEN UIP_CONF_LLH_LEN
#else /* UIP_CONF_LLH_LEN */ #else /* UIP_CONF_LLH_LEN */
#define UIP_LLH_LEN ETHER_HEADER_SIZE + RTL_FRAME_DESC_SIZE #define UIP_LLH_LEN (ETHER_HEADER_SIZE + RTL_FRAME_DESC_SIZE)
#endif /* UIP_CONF_LLH_LEN */ #endif /* UIP_CONF_LLH_LEN */
/** @} */ /** @} */