realtek: add support for Ubiquiti USW Pro XG 10 PoE

Add support for RTL9313-based Ubiquiti UniFi USW Pro XG 10 PoE switch
with 10x 10G RJ45 and 2x SFP+ ports.

Hardware
========

  - RTL9313 switch SoC
  - 512 MiB RAM
  - 32 MiB SPI-NOR flash
  - 10x 100M/1G/2.5G/5G/10G RJ45 ports via RTL8264B and RTL8261BE
  - PoE: 10x 802.3bt, 90W per port, 400W total budget
  - 2x 1G/2.5G/10G SFP+ ports
  - Buttons: 1x Reset
  - LEDs:
    - System LED white/blue
    - RGBW LED per port
  - Console: TTL 3.3V, 115200 8N1, populated 4-pin header J19,
    front to back: VCC, RX, TX and GND
    (device must be opened to access header)
  - Etherlighting feature (lighting patterns and color control)
  - Vendor firmware: U-Boot + LEDE-based Ubiquiti OS

MAC address
===========

Single MAC address in EEPROM partition, applied to all ports.

Known issues
============

  - Etherlighting not controllable, driver WIP. Port LEDs for link work
    though. By default, the controller keeps the LEDs in a breathing
    state, gated by the link state delivered by the Realtek SoC.

Disclaimer
==========

Stock firmware uses a dual-bank layout (kernel0/kernel1, ~15 MiB each).
OpenWrt replaces both banks with a single contiguous firmware partition.
Flashing OpenWrt overwrites both stock kernel slots; U-Boot remains
intact and can be used for recovery.

Installation
============

1. Enable SSH on the stock UniFi OS and log in with user account.

(at this stage you may make backups of the flash, just to be sure)

2. Copy the OpenWrt sysupgrade image to /tmp on the switch (e.g. via
   scp).

3. Adjust IMG below to point at the copied file, then run the block as a
   whole. It writes kernel0, splits into kernel1 if the image is larger
   than that slot (otherwise invalidates kernel1 so U-Boot cannot pick
   a stale bank), and reboots:

   IMG=/tmp/openwrt-realtek-rtl931x-ubnt_usw-pro-xg-10-poe-squashfs-sysupgrade.bin
   K0_BLOCKS=$((0xec0000 / 0x10000))

   dd if="$IMG" of=/dev/mtdblock2 bs=64k count=$K0_BLOCKS conv=fsync
   if [ "$(wc -c < "$IMG")" -gt $((0xec0000)) ]; then
       dd if="$IMG" of=/dev/mtdblock3 bs=64k skip=$K0_BLOCKS conv=fsync
   else
       dd if=/dev/zero of=/dev/mtdblock3 bs=64k count=1 conv=fsync
   fi
   sync
   reboot

   Then it comes up in OpenWrt.

   You may also install the image in any other way, just make sure to
   write it to the kernel0 partition and if the image is larger than
   15,104 kiB, write the rest to the kernel1 partition, otherwise invalidate
   it so U-Boot cannot boot from the second bank.

4. It is recommended to modify the bootcmd to speedup the boot and prevent
   any issues due to the dual-boot selection. Since U-Boot by default
   uses bootubnt which does a lot of (unneeded) RTK initialization,
   quite some time passes until Linux is started. Additionally, the
   U-boot logic fiddles with some bits on flash which causes JFFS2
   errors in OpenWrt. While this doesn't seem to cause issues yet, be
   defensive and set the bootcmd to:

     bootm 0xb4150000

   This directly boots the uImage from flash, without doing all the
   initialization. OpenWrt is able to bootstrap the networking completely
   on its own.

It does not matter which bank stock booted from when the dd block
runs: both banks are touched in the same pass (kernel0 written, kernel1
either written or invalidated). With kernel1 invalidated, U-Boot's
internal fallback kicks in and permanently switches to kernel0 on the
next boot, so the device stays on OpenWrt as long as kernel0 is
bootable.

Recovery
========

Since the installation procedure invalidates or partially overwrites
the second bank, recovery requires serial console access (see Hardware
above for pinout).

1. Interrupt U-Boot autoboot by spamming the Escape key during early
   boot to drop into the U-Boot prompt.

2. Bring up networking:

   rtk network on

3. Transfer an OpenWrt initramfs image via TFTP and boot it:

   setenv ipaddr <switch-ipaddr>
   setenv serverip <tftpserver-ipaddr>
   setenv bootfile openwrt-realtek-rtl931x-ubnt_usw-pro-xg-10-poe-initramfs-kernel.bin
   tftpboot
   bootm

4. From the running initramfs OpenWrt, do a sysupgrade to reflash
   OpenWrt or whatever you want to recover. There is no need for the
   complicated procedure from installation since OpenWrt sees the
   firmware partition already as a whole.

Return to stock firmware
========================

There is no fully-supported revert path. The stock firmware blob is a
Ubiquiti UBNT archive (header + parts, see firmware-utils' fw.h) that
embeds a u-boot and a kernel0 uImage payload; only the latter is
relevant when writing back to the kernel partitions.

The snippet below extracts the kernel0 uImage from such a blob by
locating the uImage offset and size using binwalk. It is provided as a
best-effort starting point; verify the result before flashing,
otherwise you're on your own:

   BLOB=US3.rtl93xx_7.4.1+X.Y.Z.bin
   binwalk $BLOB | grep uImage > uimage-info.txt
   OFF=$(cut -d\  -f1 uimage-info.txt)
   SIZE=$(sed -nE 's/^.*image size: ([0-9]+) bytes,.*/\1/p' uimage-info.txt)
   dd if="$BLOB" of=kernel0.uImage bs=1 skip="$OFF" count="$SIZE"

Once you have a clean uImage, it can be written to the kernel partition
from within OpenWrt. If you adjusted the bootcmd during installation,
make sure to restore it to the default "bootcmd=bootubnt". After a reboot,
Ubiquiti's firmware should boot.

Or, if you did backups of the flash before, just write the backup to the
flash.

(lightly modified from Jonas commit adding support for usw-pro-8-poe)

Signed-off-by: Russell Senior <russell@personaltelco.net>
Link: https://github.com/openwrt/openwrt/pull/25425
Signed-off-by: Jonas Jelonek <jonas@jonasjelonek.de>
This commit is contained in:
Russell Senior authored and Jonas Jelonek committed 2026-09-28 20:24:21 +02:00
1 parent 7a29a6bf6b
commit 8635eeedc3
3 files changed
+354

No files matched your search

@@ -103,6 +103,7 @@ realtek_setup_macs()
sirivision,sr-st3808f|\
sirivision,sr-st31212f|\
ubnt,usw-pro-max-24-poe|\
ubnt,usw-pro-xg-10-poe|\
ubnt,usw-pro-xg-8-poe|\
zyxel,gs1920-24-v1|\
zyxel,gs1920-24hp-v2)
@@ -0,0 +1,343 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/dts-v1/;
#include "rtl931x.dtsi"
#include <dt-bindings/input/input.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
#include <dt-bindings/mux/mux.h>
#include <dt-bindings/phy/phy.h>
/ {
compatible = "ubnt,usw-pro-xg-10-poe", "realtek,rtl9313-soc";
model = "UniFi USW Pro XG 10 PoE";
aliases {
label-mac-device = &ethernet0;
led-boot = &led_sys_white;
led-failsafe = &led_sys_white;
led-running = &led_sys_blue;
led-upgrade = &led_sys_white;
};
chosen {
bootargs = "console=ttyS0,115200 earlycon";
stdout-path = "serial0:115200n8";
};
memory@0 {
device_type = "memory";
reg = <0x00000000 0x10000000>, /* first 256 MiB */
<0x90000000 0x10000000>; /* remaining 256 MiB */
};
keys {
compatible = "gpio-keys";
key-reset {
label = "reset";
gpios = <&gpio0 26 GPIO_ACTIVE_LOW>;
linux,code = <KEY_RESTART>;
};
};
leds: leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinmux_disable_sys_led>;
led_sys_blue: led-0 {
color = <LED_COLOR_ID_BLUE>;
function = LED_FUNCTION_INDICATOR;
gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
};
led_sys_white: led-1 {
color = <LED_COLOR_ID_WHITE>;
function = LED_FUNCTION_INDICATOR;
gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
};
};
led_set: led_set@0 {
compatible = "realtek,rtl9300-leds";
clock-frequency = <1250000>;
active-low;
/*
* selects all speed modes to trigger a LED, two slots. This doesn't correspond
* fully to actual LED behavior. The serial stream is fed into the Etherlighting
* MCU which translates that into the LEDs, managing color, behavior etc. in
* addition.
*/
led_set0 = <(RTL93XX_LED_SET_10G | RTL93XX_LED_SET_5G | RTL93XX_LED_SET_2P5G |
RTL93XX_LED_SET_1G | RTL93XX_LED_SET_100M | RTL93XX_LED_SET_10M |
RTL93XX_LED_SET_LINK | RTL93XX_LED_SET_ACT)
(RTL93XX_LED_SET_10G | RTL93XX_LED_SET_5G | RTL93XX_LED_SET_2P5G |
RTL93XX_LED_SET_1G | RTL93XX_LED_SET_100M | RTL93XX_LED_SET_10M |
RTL93XX_LED_SET_LINK | RTL93XX_LED_SET_ACT)>;
};
sfp1: sfp-p1 {
compatible = "sff,sfp";
i2c-bus = <&i2c_sfp1>;
los-gpio = <&gpio2 0 GPIO_ACTIVE_HIGH>;
mod-def0-gpio = <&gpio2 2 GPIO_ACTIVE_LOW>;
tx-disable-gpio = <&gpio2 3 GPIO_ACTIVE_HIGH>;
tx-fault-gpio = <&gpio2 1 GPIO_ACTIVE_HIGH>;
};
sfp2: sfp-p2 {
compatible = "sff,sfp";
i2c-bus = <&i2c_sfp2>;
los-gpio = <&gpio2 4 GPIO_ACTIVE_HIGH>;
mod-def0-gpio = <&gpio2 6 GPIO_ACTIVE_LOW>;
tx-disable-gpio = <&gpio2 7 GPIO_ACTIVE_HIGH>;
tx-fault-gpio = <&gpio2 5 GPIO_ACTIVE_HIGH>;
};
};
&spi0 {
status = "okay";
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "u-boot";
reg = <0x0 0x140000>;
read-only;
};
partition@140000 {
label = "u-boot-env";
reg = <0x140000 0x10000>;
};
/*
* Vendor layout has two kernel partitions:
* (1) <0x150000 0xec0000> = "kernel0"
* (2) <0x1010000 0xed0000> = "kernel1"
*/
partition@150000 {
label = "firmware";
reg = <0x150000 0x1d90000>;
compatible = "openwrt,uimage", "denx,uimage";
};
partition@1ee0000 {
label = "cdata";
reg = <0x1ee0000 0x10000>;
read-only;
};
partition@1ef0000 {
label = "cfg";
reg = <0x1ef0000 0x100000>;
read-only;
};
partition@1ff0000 {
label = "EEPROM";
reg = <0x1ff0000 0x10000>;
read-only;
nvmem-layout {
compatible = "fixed-layout";
#address-cells = <1>;
#size-cells = <1>;
factory_macaddr: macaddr@0 {
compatible = "mac-base";
reg = <0x0 0x6>;
#nvmem-cell-cells = <1>;
};
};
};
};
};
};
&ethernet0 {
nvmem-cells = <&factory_macaddr 0>;
nvmem-cell-names = "mac-address";
};
&gpio0 {
pinctrl-names = "default";
pinctrl-0 = <&pinmux_disable_jtag>;
/*
* GPIO 1 is the global reset pin shared by all PHYs across all MDIO
* buses. It is intentionally not declared as reset-gpios on any bus:
* the MDIO driver / phylink only support a reset GPIO per bus, not on
* the parent controller. Attaching it to a single bus would still reset
* the PHYs on the other buses as a side effect, leaving their software
* state out of sync with the hardware and likely breaking them.
*/
phy_reset_hog {
gpio-hog;
gpios = <1 GPIO_ACTIVE_LOW>;
output-low;
line-name = "phy-reset";
};
};
&i2c_mst1 {
status = "okay";
i2c_sfp1: i2c@0 { reg = <0>; };
i2c_sfp2: i2c@1 { reg = <1>; };
i2c7: i2c@7 { reg = <7>; }; /* Etherlighting MCU at 0x66 */
i2c8: i2c@8 {
#address-cells = <1>;
#size-cells = <0>;
reg = <8>;
adt7475@2e {
compatible = "adi,adt7475";
reg = <0x2e>;
};
};
};
&i2c_mst2 {
status = "okay";
i2c5: i2c@5 {
#address-cells = <1>;
#size-cells = <0>;
reg = <5>;
gpio1: gpio@22 {
compatible = "nxp,pca9555";
reg = <0x22>;
gpio-controller;
#gpio-cells = <2>;
};
gpio2: gpio@24 {
compatible = "nxp,pca9555";
reg = <0x24>;
gpio-controller;
#gpio-cells = <2>;
};
};
i2c_poe: i2c@6 {
reg = <6>;
#address-cells = <1>;
#size-cells = <0>;
pse: ethernet-pse@20 {
compatible = "ubnt,usw-pro-xg-10-poe-pse", "realtek,pse-mcu-gen2-smbus";
reg = <0x20>;
reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
pse-pis {
#address-cells = <1>;
#size-cells = <0>;
PSE_PI(0)
PSE_PI(1)
PSE_PI(2)
PSE_PI(3)
PSE_PI(4)
PSE_PI(5)
PSE_PI(6)
PSE_PI(7)
PSE_PI(8)
PSE_PI(9)
};
};
};
};
&mdio_ctrl {
pinctrl-names = "default";
pinctrl-0 = <&pinmux_enable_mdc_mdio_0>,
<&pinmux_enable_mdc_mdio_1>;
};
&mdio_bus0 {
/* RTL8264B */
ethernet-phy-package@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
PHY_C45(0, 0)
PHY_C45(8, 1)
PHY_C45(16, 2)
PHY_C45(24, 3)
};
/* RTL8264B */
ethernet-phy-package@4 {
#address-cells = <1>;
#size-cells = <0>;
reg = <4>;
PHY_C45(32, 4)
PHY_C45(40, 5)
PHY_C45(48, 6)
PHY_C45(50, 7)
};
};
&mdio_bus1 {
PHY_C45(52, 0)
PHY_C45(53, 1)
};
&switch0 {
ethernet-ports {
#address-cells = <1>;
#size-cells = <0>;
SWITCH_PORT_LED(0, 1, 2, 0, 0, usxgmii)
SWITCH_PORT_LED(8, 2, 3, 0, 0, usxgmii)
SWITCH_PORT_LED(16, 3, 4, 0, 0, usxgmii)
SWITCH_PORT_LED(24, 4, 5, 0, 0, usxgmii)
SWITCH_PORT_LED(32, 5, 6, 0, 0, usxgmii)
SWITCH_PORT_LED(40, 6, 7, 0, 0, usxgmii)
SWITCH_PORT_LED(48, 7, 8, 0, 0, usxgmii)
SWITCH_PORT_LED(50, 8, 9, 0, 0, usxgmii)
SWITCH_PORT_LED(52, 9, 10, 0, 0, usxgmii)
SWITCH_PORT_LED(53, 10, 11, 0, 0, usxgmii)
SWITCH_PORT_SFP(54, 11, 12, 0, 1)
SWITCH_PORT_SFP(55, 12, 13, 0, 2)
/* CPU port */
port@56 {
ethernet = <&ethernet0>;
reg = <56>;
phy-mode = "internal";
fixed-link {
speed = <1000>;
full-duplex;
};
};
};
};
&phy0 { pses = <&pse_pi0>; };
&phy8 { pses = <&pse_pi1>; };
&phy16 { pses = <&pse_pi2>; };
&phy24 { pses = <&pse_pi3>; };
&phy32 { pses = <&pse_pi4>; };
&phy40 { pses = <&pse_pi5>; };
&phy48 { pses = <&pse_pi6>; };
&phy50 { pses = <&pse_pi7>; };
&phy52 { pses = <&pse_pi8>; };
&phy53 { pses = <&pse_pi9>; };
+10
View File
@@ -76,6 +76,16 @@ define Device/sirivision_sr-st31212f
endef
TARGET_DEVICES += sirivision_sr-st31212f
define Device/ubnt_usw-pro-xg-10-poe
SOC := rtl9313
DEVICE_VENDOR := Ubiquiti
DEVICE_MODEL := UniFi USW Pro XG 10 PoE
IMAGE_SIZE := 30272k
DEVICE_PACKAGES := rtl826x-firmware kmod-hwmon-adt7475 kmod-pse-realtek-mcu-i2c
$(Device/kernel-lzma)
endef
TARGET_DEVICES += ubnt_usw-pro-xg-10-poe
define Device/ubnt_usw-pro-xg-8-poe
SOC := rtl9313
DEVICE_VENDOR := Ubiquiti