Support for RTL838x based managed switches

I just thought of another thing to try: I made vlan1 tagged on lan9. Looks like the dhcp reply makes it back out of the switch, untagged!

That is: lan1 has vlan1 untagged and vlan10 tagged. port 10 has vlan1 tagged and vlan10 untagged. Yet, the dhcp packets are ending up untagged on both.

I've now changed vlan10 into vlan100 so it's easier to visually distinguish.

EDIT: seems like suddenly, my 2.5G ports stopped being able to send any traffic... and now, I can't even ping anything from the switch OS (via serial console). Booted back to stock, and they work there. Booted back to openwrt, still can't ping in or out from switch OS. So I've flashed back to stock.

Should you not have lan1 tagged for the vlans used on ports 9 and 10 so that packets going out of that port keep their tags? My understanding is that untagged ports add a tag on ingress if they have a pvid configured and will always strip a tag on egress.

My understanding of VLAN tags (I'm not an expert) is this:

Inside the switch, all VLANs have tags, though the binary tag format may be proprietary instead of 802.1q. The PVID determines what VID gets slapped on incoming untagged packets, and Egress Untagged or Egress Tagged for a VID determines whether tags for that VID are stripped upon exiting that port.

What should happen:

PC (untagged) ⟹ lan9 (PVID=100) ⟹ switch (tag=100) ⟹ lan1 (egress tagged) ⟹ Router (tag=100)


Router (tag=100) ⟹ lan1 (tag=100) ⟹ switch (tag=100) ⟹ lan9 (egress UNtagged) ⟹ PC (untagged)

What seems to be happening:


PC (untagged) ⟹ lan9 (PVID=100) ⟹ switch (tag=100) ⟹ lan1 (tag goes poof) ⟹ Router (untagged)


Router (untagged due to broken request) ⟹ lan1 (PVID=1) ⟹ switch (tag=1) ⟹ discard (lan9 isn't allowed to see vlan 1)

Response with tweak (tagged vlan 1 for debugging):

Router (untagged due to broken request) ⟹ lan1 (PVID=1) ⟹ switch (tag=1) ⟹ lan9 (tag goes poof) ⟹ PC (untagged)

OK, I'll look into it.

The issue that still confuses me is this picture with two (inner+outer) tags on the switch. This is easy to understand if we talk about q-in-q, and ports where we push or pop one tag at a time. But most of the time we don't deal with q-in-q. Still, all packets have two tags internal on the switch. If you have a packet wih vlan=100 coming in on an port with PVID=10,10, will 100 be the considered the inner or outer VLAN? How about if we have PVID=100,10? or PVID=10,100?

OK, I should probably sit down with a pen and paper and try this out. But it's easier to ask :slight_smile:


  1. I want realtek-poe to work, but to do so we need an agreed-upon place for it to be developed
  2. I don't think realtek-poe will make it to mainline OpenWrt, but as it stands there's no OpenWrt-based implementation that can successfully control this very commonly seen MCU+BCM59XXX-backed PoE hardware, and I think realtek-poe is useful as an open-source reference implementation for debugging.

On my GS1900-24HPv1, the issue I'm replying to was caused by MAX_PORTS being hard-set as 8 in realtek-poe's main.c. After changing that (and another instance which hard-codes 8 in a for loop in poe_reply_port_overview), the program runs, but does not successfully initialize PoE on any ports (so far that I've tested). I think this is because of a port mapping issue which must be dealt with for these 24-port switches.

Two things in particular:

  1. The 0x0ath sent frame always receives a response 0xfe, AKA 'Request Checksum Frame was incorrect'. I've tested multiple different configurations and found that no matter what the frame is, as long as it's the 0x0ath one, it will fail. I don't believe this is true when the Frame ID wraps over to 0x0a again.

  2. Frames sent to manipulate configuration on port 0x0a always fail with response 0xfe -- and frames to manipulate port 0x0d respond with information from port 0x0a. I suspect this has to do with port mapping, and that we will have to find the original firmware's port mapping and reproduce it.

I have implemented a ubus callback to queue up one frame:

root@OpenWrt:/# ubus -v list poe
'poe' @38a3bba9

Using (roughly) this patch:

@ -773,6 +866,47 @@ ubus_poe_info_cb(struct ubus_context *ctx, struct ubus_object *obj,
     return UBUS_STATUS_OK;

+enum {
+static const struct blobmsg_policy ubus_poe_sendframe_policy[__POE_SENDFRAME_MAX] = {
+static int
+ubus_poe_sendframe_cb(struct ubus_context *ctx, struct ubus_object *obj,
+           struct ubus_request_data *req, const char *method,
+           struct blob_attr *msg)
+    struct blob_attr *tb[__POE_SENDFRAME_MAX];
+    blobmsg_parse(ubus_poe_sendframe_policy, __POE_SENDFRAME_MAX, tb, blob_data(msg), blob_len(msg));
+    char *frame = blobmsg_get_string(tb[POE_SENDFRAME_STRING]);
+    unsigned int frame_strlen = strlen(frame);
+    unsigned char cmd[9];
+    unsigned int cmdsize = 0;
+    unsigned char *end = cmd + sizeof(cmd);
+    unsigned int u;
+    unsigned int i = 0;
+    while ((cmd + cmdsize < end) && (i < frame_strlen)) {
+        if (sscanf((frame+i), "0x%2x", &u) == 1) {
+            cmd[cmdsize++] = u;
+            i += 4;
+        } else
+            i += 1;
+    }
+    return poe_cmd_queue(cmd, cmdsize);
 static int
 ubus_poe_reload_cb(struct ubus_context *ctx, struct ubus_object *obj,
            struct ubus_request_data *req, const char *method,
@@ -787,6 +921,7 @@ ubus_poe_reload_cb(struct ubus_context *ctx, struct ubus_object *obj,
 static const struct ubus_method ubus_poe_methods[] = {
     UBUS_METHOD_NOARG("info", ubus_poe_info_cb),
     UBUS_METHOD_NOARG("reload", ubus_poe_reload_cb),
+    UBUS_METHOD("sendframe", ubus_poe_sendframe_cb, ubus_poe_sendframe_policy),

This will enqueue up to 9 bytes as a frame (using the same code path as all of the frame command senders to handle frame number / checksumming); unfortunately I'm not a strong C programmer and haven't thought of a good way to retrieve the response to the frame as the frame is only enqueued and not waited on -- but I might be misunderstanding the running context of the ubus_poe_sendframe_cb when one calls ubus call poe sendframe '{"frame": "0x01 0x01"}' (to enable port 1, for example); it might be possible to have the method here spin / wait for the frame response, I'm just not really familiar with the ubus architecture.

To be clear, the code does work and does send the frame you ask; if running realtek-poe -d you do see the frame being sent upon running that ubus call.

It'd be nice to have a place to send my patches, or even an official home for realtek-poe to be developed, but ca n'existe pas at this point. I'm happy to create a repository / specific branch for code development on Github if that would help centralize efforts, @blogic.


I can spread this realtek-poe-based stuff to another thread.


Wait, the switch is doing double tagging? That seems odd and unnecessary, at least to me. I wonder why?

Guessing the internal double tags are necessary to be able to support q-in-q. Or alternatively more than 1 bridge group on the same switch with overlapping VLANs.

The RTL switch hardware supports an Outer and an Inner Tag on any packet in order to support Q-in-Q. A switching decision is only based on one of these tags at a point in time in the switch, never on both simultaneously.

i have used dgs-1210-28P F2 openwrt firmware for 14 months with 5 clients, only default vlan 1 on all ports . There were not any problem. Thanks to developers..

1 Like

I've created pages at Linux support for Realtek switches and WikiDevi for the ZyXEL GS1900-24EP with details I have been able to gather so far. I've also requested the GPL source code from ZyXEL. I'm hoping it will provide additional information regarding the RTL8238 PSE Controllers. I'm guessing they are 8 port controllers with only 4 ports used on one for the 12 PoE ports on this switch. I was unable to remove the heat sinks, so hoping someone can tell me how to identify the model of the 2 8-port PHY extenders used.

1 Like

One way to identify PHYs is to look a the results of the mdio bus being probed in order to identify all PHYs. This is done automatically by the DSA subsystem. To see the result, you need to add the following into rtl838x.c:rtl838x_read_phy() in a new line before the "return 0;" statement:

	pr_info("%s: port %d, page: %d, reg: %x, val: %x\n",
		__func__, port, page, reg, *val);

The PHY registers 2 and 3 in page 0 then define the PHY-ID. The PHYs so far encountered can be seen in rtl83xx-phy.h . My bet would be on 2 external RTL8218B.

I have now submitted a pull request for ZyXEL GS1900-24E. Would appreciate anyone adding a Tested By or reviewing the pull request.

1 Like

I have the DGS-1210-24p and was able to flash OpenWrt 21.02.3 onto it.
However i am not able to steer the fan speeds for now. The fans are off and i cannot make them turn on.

I believe I have an LM63 (by looking into the orig. G-Level Firmware from D-Link)

Did you manage to steer the fans?

No, I traced out the pins from the expander that are used for I2C bit-banging, but bit-banging would fail as SDA would just float.
It looks like a potentially pull-up is missing.

Havent looked into it for months, its on the TO-DO list along with tracing the 2 remaining SFP slots

tnx.. are your fans working on power Up?

the reason i am asking:
at the moment the switch is sitting on my table and has just 1 device is connected to the Switch (my PC), no PoE active. However when i read out the temperatures from the poe-ports (happy to provide my lua script) the temperature is steadily rising to beyond 35 degrees C.

I also saw that the orig Firmware is interacting with ioctls to /dev/rtcore, i assume this is closed source?

No, I think that they are disabled by default.

I have possibly some dumb questions:

  • the ports for bitbanging are GPIOs (do you remember which ones)?
  • and bitbanging means we set the GPIOs to 0/1 to simulate i2c comms to the LM63?
    is there some PoC code that does that already so i can try to proceed there?


As far as I remember it was GPIO30 and 31 on the RTL8321.
You just set-up a DTS node using i2c-gpio