Support for XikeStor SKS8300-8T (RTL9300)

I recently acquired an SKS8300-8T switch, which uses the Realtek RTL9303 SoC with 8x rj45 REALTEK 8261BE 10GbE ports. After a bit of research and based on the work done with the SKS8300-8X switch, I was able to fairly easily boot into OpenWRT. The only problem is that there's a bug with the LAN ports, when a cable is plugged into ports 1-4, the corresponding ports 5-8 are incorrectly activated (port pairing) and ports 5-8 cannot function independently.

So after I found a remote code execution vulnerability in the device, I gained root access to the stock firmware and started gathering information about the device.

/www_wayos # cat /proc/mtd
dev:    size   erasesize  name
mtd0: 001c0000 00010000 "LOADER"
mtd1: 00010000 00010000 "BDINFO"
mtd2: 00010000 00010000 "SYSINFO"
mtd3: 00010000 00010000 "JFFS2 FACTORYDATA"
mtd4: 00010000 00010000 "JFFS2 SYSDATA"
mtd5: 00a00000 00010000 "JFFS2 FILESYSTEM"
mtd6: 01400000 00010000 "RUNTIME"
mtd7: 02000000 00010000 "RUNTIME2"
/www_wayos # cat /proc/cpuinfo
system type             : RTL9300
machine                 : RTL9300
processor               : 0
cpu model               : MIPS 34Kc V5.5
BogoMIPS                : 530.41
wait instruction        : yes
microsecond timers      : yes
tlb_entries             : 32
extra interrupt vector  : yes
hardware watchpoint     : no
isa                     : mips1 mips32r2
ASEs implemented        : mips16
shadow register sets    : 1
kscratch registers      : 0
package                 : 0
core                    : 0

And here is my current DTS file:

// SPDX-License-Identifier: GPL-2.0-or-later
#include "rtl930x.dtsi"

#include <dt-bindings/input/input.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
#include <dt-bindings/thermal/thermal.h>

/ {
  compatible = "xikestor,sks8300-8t", "realtek,rtl930x-soc";
  model = "XikeStor SKS8300-8T";

  memory@0 {
    device_type = "memory";
    reg = <0x00000000 0x10000000>,
          <0x20000000 0x10000000>; 
  };

  aliases {
    led-boot = &led_sys;
    led-failsafe = &led_sys;
    led-running = &led_sys;
    led-upgrade = &led_sys;
  };

  chosen {
    stdout-path = "serial0:115200n8";
  };

  i2c_master: i2c@1b00036c {
    compatible = "realtek,rtl9300-i2c";
    reg = <0x1b00036c 0x3c>;
    #address-cells = <1>;
    #size-cells = <0>;
    scl-pin = <8>;
    sda-pin = <9>;
    clock-frequency = <100000>;

    fan_controller: fan-controller@6a {
      compatible = "realtek,fan-controller";
      reg = <0x6a>;
      status = "okay";
    };
  };

  keys {
    compatible = "gpio-keys";

    button-reset {
      label = "reset";
      gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
      linux,code = <KEY_RESTART>;
    };
  };

  leds {
    compatible = "gpio-leds";

    led_sys: led-0 {
      gpios = <&gpio0 23 GPIO_ACTIVE_HIGH>;
      color = <LED_COLOR_ID_GREEN>;
      function = LED_FUNCTION_STATUS;
    };
  };

  led_set {
    compatible = "realtek,rtl9300-leds";
    active-low;

    /*
     * LED set 0
     *
     * - LED[0](Green): 10G/LINK/ACT
     * - LED[1](Amber): 10M/100M/1G/2.5G/5G/LINK/ACT
     */
    led_set0 = <0x0baa 0x0a01>;
  };

  temp_sensor: temp-sensor@48 {
    compatible = "national,lm75";
    reg = <0x48>;
  };

  thermal-zones {
      main_thermal: main-thermal {
          polling-delay = <5000>;

          thermal-sensors = <&temp_sensor 0>;

          trips {
              fan_on: fan-on {
                  temperature = <50000>;
                  hysteresis = <2000>;
                  type = "active";
              };
          };

          cooling-maps {
              map0 {
                  trip = <&fan_on>;
                  cooling-device = <&fan_controller 0 1>;
              };
          };
      };
  };
};

&mdio_aux {
  status = "okay";

  gpio1: gpio@0 {
    compatible = "realtek,rtl8231";
    reg = <0>;

    gpio-controller;
    #gpio-cells = <2>;
    gpio-ranges = <&gpio1 0 0 37>;
    status = "okay";

    led-controller {
      compatible = "realtek,rtl8231-leds";
      status = "disabled";
    };
  };
};

&spi0 {
  status = "okay";

  flash@0 {
    compatible = "jedec,spi-nor";
    reg = <0>;
    spi-max-frequency = <10000000>;

    partitions {
      compatible = "fixed-partitions";
      #address-cells = <1>;
      #size-cells = <1>;

      partition@0 {
        label = "u-boot";
        reg = <0x0 0x100000>;
        read-only;
      };

      /* "flash_raw" on stock */
      partition@100000 {
        label = "board-info";
        reg = <0x100000 0x30000>;
        read-only;
      };

      partition@130000 {
        label = "syslog";
        reg = <0x130000 0xd0000>;
        read-only;
      };

      /* "flash_user" on stock */
      partition@200000 {
        compatible = "fixed-partitions";
        label = "firmware";
        reg = <0x200000 0x1e00000>;
        #address-cells = <1>;
        #size-cells = <1>;

        partition@0 {
          label = "kernel";
          reg = <0x0 0x800000>;
        };

        partition@800000 {
          label = "rootfs";
          reg = <0x800000 0x1600000>;
        };
      };
    };
  };
};

&ethernet0 {
    mdio: mdio-bus {
        compatible = "realtek,rtl838x-mdio";
        regmap = <&ethernet0>;
        #address-cells = <1>;
        #size-cells = <0>;

        phy0: ethernet-phy@0 {
            compatible = "ethernet-phy-ieee802.3-c45";
            phy-is-integrated;
            reg = <0>;
            sds = <2>;
        };

        phy8: ethernet-phy@8 {
            compatible = "ethernet-phy-ieee802.3-c45";
            phy-is-integrated;
            reg = <8>;
            sds = <3>;
        };

        phy16: ethernet-phy@16 {
            compatible = "ethernet-phy-ieee802.3-c45";
            phy-is-integrated;
            reg = <16>;
            sds = <4>;
        };

        phy20: ethernet-phy@20 {
            compatible = "ethernet-phy-ieee802.3-c45";
            phy-is-integrated;
            reg = <20>;
            sds = <5>;
        };

        phy24: ethernet-phy@18 {
            compatible = "ethernet-phy-ieee802.3-c45";
            phy-is-integrated;
            reg = <24>;
            sds = <6>;
        };

        phy25: ethernet-phy@19 {
            compatible = "ethernet-phy-ieee802.3-c45";
            phy-is-integrated;
            reg = <25>;
            sds = <7>;
        };

        phy26: ethernet-phy@1a {
            compatible = "ethernet-phy-ieee802.3-c45";
            phy-is-integrated;
            reg = <26>;
            sds = <8>;
        };

        phy27: ethernet-phy@1b {
            compatible = "ethernet-phy-ieee802.3-c45";
            phy-is-integrated;
            reg = <27>;
            sds = <9>;
        };
    };
};

&switch0 {
    ports {
        #address-cells = <1>;
        #size-cells = <0>;

        port@0 {
            reg = <0>;
            label = "lan1";
            phy-handle = <&phy0>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@8 {
            reg = <8>;
            label = "lan2";
            phy-handle = <&phy8>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@16 {
            reg = <16>;
            label = "lan3";
            phy-handle = <&phy16>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@20 {
            reg = <20>;
            label = "lan4";
            phy-handle = <&phy20>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@24 {
            reg = <24>;
            label = "lan5";
            phy-handle = <&phy24>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@25 {
            reg = <25>;
            label = "lan6";
            phy-handle = <&phy25>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@26 {
            reg = <26>;
            label = "lan7";
            phy-handle = <&phy26>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@27 {
            reg = <27>;
            label = "lan8";
            phy-handle = <&phy27>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@28 {
            ethernet = <&ethernet0>;
            reg = <28>;
            phy-mode = "internal";
            fixed-link {
                speed = <10000>;
                full-duplex;
            };
        };
    };
};

For the port mapping I used this https://svanheule.net/switches/rtl93xx#section9303

  - Port 1 (PHY 0) β†’ SerDes 2
  - Port 2 (PHY 8) β†’ SerDes 3
  - Port 3 (PHY 16) β†’ SerDes 4
  - Port 4 (PHY 20) β†’ SerDes 5
  - Port 5 (PHY 24) β†’ SerDes 6
  - Port 6 (PHY 25) β†’ SerDes 7
  - Port 7 (PHY 26) β†’ SerDes 8
  - Port 8 (PHY 27) β†’ SerDes 9

And after a bit of work, I finally found the binary that sets up the SerDes, fan functionalities, LEDs, etc..., located at /bin/smdrm, which I then decompiled with Ghidra. I then managed get all the functions to rewrite the driver and add support for USXGMII with auto-negotiation in /target/linux/realtek/files-6.6/drivers/net but all my attempts ended in failure...

If a Realtek driver expert happens to read this, I would really appreciate some help or guidance.

EDIT: bootloader with some debug added https://pastebin.com/5yiVT67c
EDIT 2: I have also uploaded all the reversed functions from the stock firmware related to SerDes and USXGMII initialization https://pastebin.com/2vKG9Buu

3 Likes

Someone like @svanheule ?

Check out this PR. Similar hardware. Might give you some ideas.

I already tried with CONFIG_RTL8261N_PHY=y enabled, and the behavior is the same (port pairing issue), except that it sets the 10Gbps mode by default even when I plug in a 1Gbps cable. I also tried modifying the driver for the RTL8261N in target/linux/generic/files/drivers/net/phy/rtl8261n and tweaking some functions to match the ones extracted from the stock firmware but I got the same behavior...

EDIT: bootloader using RTL8261N driver https://pastebin.com/MkvbbtDz

1 Like

Also see https://github.com/openwrt/openwrt/pull/19081

Some tips for you:

3 Likes

Thanks for the tips! It worked!

I was heading in the wrong direction from the beginning. The key was finding the correct rtl9300,smi-address values and removing phy-is-integrated.

Appreciate the pointer to the Realtek docs too!

2 Likes

I have one more question. The device has a fan on the board and after some time I was able to figure out how it works and wrote a Bash script to control it. But since that's obviously not the best solution, I'd like the fan to start automatically on boot. I tried adding it to the DTS file but that didn't work. Do you have any tips for that part as well?

Here's the Bash script implementation:

#!/bin/ash

# FAN Control Script for XikeStor - SKS8300 series
# Temperature sensor: LM75 at address 0x48

# I2C Configuration
I2C_BUS=0
I2C_ADDR=0x6a        # FAN controller
TEMP_ADDR=0x48       # LM75 temperature sensor

# Initialize FAN controller
fan_init() {
    echo "Initializing FAN controller..."
    
    # Check if chip exists
    if ! i2cget -y $I2C_BUS $I2C_ADDR 0xfe &>/dev/null; then
        echo "Error: FAN controller not detected"
        return 1
    fi
    
    # Initialization sequence
    i2cset -y $I2C_BUS $I2C_ADDR 0x4e 0x32
    i2cset -y $I2C_BUS $I2C_ADDR 0x48 0x1e
    i2cset -y $I2C_BUS $I2C_ADDR 0x40 0x6e
    i2cset -y $I2C_BUS $I2C_ADDR 0x11 0x55
    i2cset -y $I2C_BUS $I2C_ADDR 0x10 0x00
    
    echo "FAN controller initialized"
}

# Start FAN at normal speed (controlled mode)
fan_start() {
    echo "Starting FAN at normal speed..."
    
    i2cset -y $I2C_BUS $I2C_ADDR 0x11 0x55    # Enable
    i2cset -y $I2C_BUS $I2C_ADDR 0x40 0x6e    # Controlled mode
    i2cset -y $I2C_BUS $I2C_ADDR 0x48 0x1e    # Default speed
    
    echo "FAN started at normal speed"
}

# Start FAN at full speed
fan_start_full() {
    echo "Starting FAN at full speed..."
    
    i2cset -y $I2C_BUS $I2C_ADDR 0x40 0x00    # Full speed mode
    
    echo "FAN started at full speed"
}

# Stop FAN
fan_stop() {
    echo "Stopping FAN..."
    
    i2cset -y $I2C_BUS $I2C_ADDR 0x40 0xff    # Turn OFF
    
    echo "FAN stopped"
}

# Get current RPM
fan_get_rpm() {
    local rpm_reg=$(i2cget -y $I2C_BUS $I2C_ADDR 0x09 2>/dev/null)
    
    if [ -z "$rpm_reg" ]; then
        echo "Error: Failed to read RPM"
        return 1
    fi
    
    # Check FAN mode
    local mode=$(i2cget -y $I2C_BUS $I2C_ADDR 0x40 2>/dev/null)
    
    if [ "$mode" = "0xff" ]; then
        echo "FAN RPM: 0 (FAN is OFF)"
        return 0
    fi
    
    if [ "$rpm_reg" = "0xff" ]; then
        if [ "$mode" = "0x00" ]; then
            echo "FAN RPM: Maximum (full speed mode)"
        else
            echo "FAN RPM: Normal speed"
        fi
        return 0
    fi
    
    # Calculate actual RPM if we get a valid reading
    local dec_value=$((rpm_reg))
    local shifted=$(( (dec_value >> 4) & 0x0F ))
    
    if [ $shifted -eq 0 ]; then
        echo "FAN RPM: 0"
        return 0
    fi
    
    # RPM = 0x1e0000 / ((value >> 4) << 1)
    local rpm=$(( 1966080 / (shifted << 1) ))
    echo "FAN RPM: $rpm"
}

# Get temperature from LM75 sensor
fan_get_temp() {
    # LM75 temperature sensor at address 0x48
    # Register 0x00 contains temperature data (2 bytes)
    
    # Check if LM75 exists
    if ! i2cget -y $I2C_BUS $TEMP_ADDR 0x00 w &>/dev/null; then
        echo "Temperature: Sensor not detected"
        return 1
    fi
    
    # Read temperature (word read for 2 bytes)
    local temp_raw=$(i2cget -y $I2C_BUS $TEMP_ADDR 0x00 w 2>/dev/null)
    
    if [ -z "$temp_raw" ]; then
        echo "Temperature: Read error"
        return 1
    fi
    
    # Convert from hex to decimal
    local temp_dec=$((temp_raw))
    
    # LM75 format: 
    # - Bytes are swapped in word read (LSB MSB)
    # - Need to swap bytes back: ((temp & 0xFF) << 8) | ((temp >> 8) & 0xFF)
    local temp_swapped=$(( ((temp_dec & 0xFF) << 8) | ((temp_dec >> 8) & 0xFF) ))
    
    # Temperature is in upper 9 bits, in 0.5Β°C increments
    # Shift right by 7 to get temperature in 0.5Β°C units
    local temp_half=$(( temp_swapped >> 7 ))
    
    # Check if negative (bit 8 set)
    if [ $(( temp_half & 0x100 )) -ne 0 ]; then
        # Negative temperature (two's complement)
        temp_half=$(( temp_half - 512 ))
    fi
    
    # Convert to degrees Celsius
    local temp_int=$(( temp_half / 2 ))
    local temp_frac=$(( (temp_half % 2) * 5 ))
    
    echo "Temperature: ${temp_int}.${temp_frac}Β°C"
}

# Get FAN status
fan_status() {
    echo "=== FAN Status ==="
    
    # Read control registers
    local reg_40=$(i2cget -y $I2C_BUS $I2C_ADDR 0x40 2>/dev/null)
    local reg_11=$(i2cget -y $I2C_BUS $I2C_ADDR 0x11 2>/dev/null)
    
    echo -n "FAN State: "
    case "$reg_40" in
        "0x00") 
            echo "ON (Full speed mode)"
            ;;
        "0x6e") 
            echo "ON (Normal speed mode)"
            ;;
        "0xff") 
            echo "OFF"
            ;;
        *) 
            echo "Unknown (0x40=$reg_40)"
            ;;
    esac
    
    # Show RPM
    fan_get_rpm
    
    # Show temperature
    fan_get_temp
    
    echo "=================="
}

# Main script
case "$1" in
    init)
        fan_init
        ;;
    start)
        fan_start
        ;;
    start-full)
        fan_start_full
        ;;
    stop)
        fan_stop
        ;;
    rpm)
        fan_get_rpm
        ;;
    temp)
        fan_get_temp
        ;;
    status)
        fan_status
        ;;
    *)
        echo "FAN Control Script for OpenWRT"
        echo "Usage: $0 {init|start|start-full|stop|rpm|temp|status}"
        echo ""
        echo "Commands:"
        echo "  init       - Initialize FAN controller"
        echo "  start      - Start FAN at normal speed"
        echo "  start-full - Start FAN at full speed"
        echo "  stop       - Stop the FAN"
        echo "  rpm        - Get current RPM"
        echo "  temp       - Get temperature from LM75 sensor"
        echo "  status     - Show FAN status"
        echo ""
        echo "Examples:"
        echo "  $0 init       # Initialize on first use"
        echo "  $0 start      # Start at normal speed"
        echo "  $0 start-full # Start at maximum speed"
        echo "  $0 stop       # Stop FAN"
        echo "  $0 status     # Check current status"
        exit 1
        ;;
esac

exit 0

Here is my current DTS file

// SPDX-License-Identifier: GPL-2.0-or-later

#include "rtl930x.dtsi"

#include <dt-bindings/input/input.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
#include <dt-bindings/thermal/thermal.h>

/ {
  compatible = "xikestor,sks8300-8t", "realtek,rtl930x-soc";
  model = "XikeStor SKS8300-8T";

  memory@0 {
    device_type = "memory";
    reg = <0x00000000 0x10000000>, /* first 256 MiB */
          <0x20000000 0x10000000>; /* remaining 256 MiB */
  };

  aliases {
    led-boot = &led_sys;
    led-failsafe = &led_sys;
    led-running = &led_sys;
    led-upgrade = &led_sys;
  };

  chosen {
    stdout-path = "serial0:115200n8";
  };

  i2c_master: i2c@1b00036c {
    compatible = "realtek,rtl9300-i2c";
    reg = <0x1b00036c 0x3c>;
    #address-cells = <1>;
    #size-cells = <0>;
    scl-pin = <8>;
    sda-pin = <9>;
    clock-frequency = <100000>;

    fan_controller: fan-controller@6a {
      compatible = "realtek,fan-controller";
      reg = <0x6a>;
      status = "okay";
    };
  };

  keys {
    compatible = "gpio-keys";

    button-reset {
      label = "reset";
      gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
      linux,code = <KEY_RESTART>;
    };
  };

  leds {
    compatible = "gpio-leds";

    led_sys: led-0 {
      gpios = <&gpio0 23 GPIO_ACTIVE_HIGH>;
      color = <LED_COLOR_ID_GREEN>;
      function = LED_FUNCTION_STATUS;
    };
  };

  led_set {
    compatible = "realtek,rtl9300-leds";
    active-low;

    /*
     * LED set 0
     *
     * - LED[0](Green): 10G/LINK/ACT
     * - LED[1](Amber): 10M/100M/1G/2.5G/5G/LINK/ACT
     */
    led_set0 = <0x0baa 0x0a01>;
  };

  temp_sensor: temp-sensor@48 {
    compatible = "national,lm75";
    reg = <0x48>;
  };

  thermal-zones {
      main_thermal: main-thermal {
          polling-delay = <5000>;

          thermal-sensors = <&temp_sensor 0>;

          trips {
              fan_on: fan-on {
                  temperature = <50000>;
                  hysteresis = <2000>;
                  type = "active";
              };
          };

          cooling-maps {
              map0 {
                  trip = <&fan_on>;
                  cooling-device = <&fan_controller 0 1>;
              };
          };
      };
  };
};

&mdio_aux {
  status = "okay";

  gpio1: gpio@0 {
    compatible = "realtek,rtl8231";
    reg = <0>;

    gpio-controller;
    #gpio-cells = <2>;
    gpio-ranges = <&gpio1 0 0 37>;
    status = "okay";

    led-controller {
      compatible = "realtek,rtl8231-leds";
      status = "disabled";
    };
  };
};

&spi0 {
  status = "okay";

  flash@0 {
    compatible = "jedec,spi-nor";
    reg = <0>;
    spi-max-frequency = <10000000>;

    partitions {
      compatible = "fixed-partitions";
      #address-cells = <1>;
      #size-cells = <1>;

      partition@0 {
        label = "u-boot";
        reg = <0x0 0x100000>;
        read-only;
      };

      /* "flash_raw" on stock */
      partition@100000 {
        label = "board-info";
        reg = <0x100000 0x30000>;
        read-only;
      };

      partition@130000 {
        label = "syslog";
        reg = <0x130000 0xd0000>;
        read-only;
      };

      /* "flash_user" on stock */
      partition@200000 {
        compatible = "fixed-partitions";
        label = "firmware";
        reg = <0x200000 0x1e00000>;
        #address-cells = <1>;
        #size-cells = <1>;

        partition@0 {
          label = "kernel";
          reg = <0x0 0x800000>;
        };

        partition@800000 {
          label = "rootfs";
          reg = <0x800000 0x1600000>;
        };
      };
    };
  };
};

&ethernet0 {
    mdio: mdio-bus {
        compatible = "realtek,rtl838x-mdio";
        regmap = <&ethernet0>;
        #address-cells = <1>;
        #size-cells = <0>;

        phy0: ethernet-phy@0 {
            compatible = "ethernet-phy-ieee802.3-c45";
            reg = <0>;
            sds = <2>;
            rtl9300,smi-address = <0 0>;
        };

        phy8: ethernet-phy@8 {
            compatible = "ethernet-phy-ieee802.3-c45";
            reg = <8>;
            sds = <3>;
            rtl9300,smi-address = <0 1>;
        };

        phy16: ethernet-phy@16 {
            compatible = "ethernet-phy-ieee802.3-c45";
            reg = <16>;
            sds = <4>;
            rtl9300,smi-address = <0 2>;
        };

        phy20: ethernet-phy@20 {
            compatible = "ethernet-phy-ieee802.3-c45";
            reg = <20>;
            sds = <5>;
            rtl9300,smi-address = <0 3>;
        };

        phy24: ethernet-phy@24 {
            compatible = "ethernet-phy-ieee802.3-c45";
            reg = <24>;
            sds = <6>;
            rtl9300,smi-address = <1 0>;
        };

        phy25: ethernet-phy@25 {
            compatible = "ethernet-phy-ieee802.3-c45";
            reg = <25>;
            sds = <7>;
            rtl9300,smi-address = <1 1>;
        };

        phy26: ethernet-phy@26 {
            compatible = "ethernet-phy-ieee802.3-c45";
            reg = <26>;
            sds = <8>;
            rtl9300,smi-address = <1 2>;
        };

        phy27: ethernet-phy@27 {
            compatible = "ethernet-phy-ieee802.3-c45";
            reg = <27>;
            sds = <9>;
            rtl9300,smi-address = <1 3>;
        };
    };
};

&switch0 {
    ports {
        #address-cells = <1>;
        #size-cells = <0>;

        port@0 {
            reg = <0>;
            label = "lan1";
            phy-handle = <&phy0>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@8 {
            reg = <8>;
            label = "lan2";
            phy-handle = <&phy8>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@16 {
            reg = <16>;
            label = "lan3";
            phy-handle = <&phy16>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@20 {
            reg = <20>;
            label = "lan4";
            phy-handle = <&phy20>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@24 {
            reg = <24>;
            label = "lan5";
            phy-handle = <&phy24>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@25 {
            reg = <25>;
            label = "lan6";
            phy-handle = <&phy25>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@26 {
            reg = <26>;
            label = "lan7";
            phy-handle = <&phy26>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@27 {
            reg = <27>;
            label = "lan8";
            phy-handle = <&phy27>;
            phy-mode = "usxgmii";
            led-set = <0>;
        };

        port@28 {
            ethernet = <&ethernet0>;
            reg = <28>;
            phy-mode = "internal";
            fixed-link {
                speed = <10000>;
                full-duplex;
            };
        };
    };
};
1 Like

Sorry no idea about this area. Have a look at the master rtl838x thread.

Request from my side: Please add the device via pull request.

Yes, I plan to submit a proper pull request to add support for the device. I'm currently in the final testing phase before doing so.

The initramfs image boots fine over TFTP via U-Boot (bootm after tftpboot), but I'm running into an issue with persistent installation. After flashing the sysupgrade image using sysupgrade -n, the device fails to boot, and U-Boot throws:

## Invalid image header
## Boot from partition 0 failed.
## Boot from both partitions are all failed.

I'm not entirely sure what's causing the issue but I suspect it might be related to the partitioning defined in my DTS file.

&spi0 {
  status = "okay";
  flash@0 {
    compatible = "jedec,spi-nor";
    reg = <0>;
    spi-max-frequency = <10000000>;
    partitions {
      compatible = "fixed-partitions";
      #address-cells = <1>;
      #size-cells = <1>;
      partition@0 {
        label = "u-boot";
        reg = <0x0 0x1c0000>;  // Match stock LOADER size
        read-only;
      };
      /* "flash_raw" on stock */
      partition@1c0000 {
        label = "board-info";
        reg = <0x1c0000 0x10000>;  // Match stock BDINFO size
        read-only;
      };
      partition@1d0000 {
        label = "sysinfo";
        reg = <0x1d0000 0x10000>;  // Match stock SYSINFO size
        read-only;
      };
      partition@1e0000 {
        label = "factory-data";
        reg = <0x1e0000 0x10000>;  // Match stock JFFS2_FACTORYDATA size
      };
      partition@1f0000 {
        label = "jffs";
        reg = <0x1f0000 0x10000>;  // Match stock JFFS2_SYSDATA size
      };
      partition@200000 {
        label = "jffs2";
        reg = <0x200000 0xa00000>;  // Match stock JFFS2_FILESYSTEM
      };
      partition@c00000 {
        compatible = "fixed-partitions";
        label = "firmware";
        reg = <0xc00000 0x1400000>;  // Match stock RUNTIME1 size
        #address-cells = <1>;
        #size-cells = <1>;
        partition@0 {
          label = "kernel";
          reg = <0x0 0x800000>;
        };
        partition@800000 {
          label = "rootfs";
          reg = <0x800000 0xc00000>; 
        };
      };
    };
  };
};

It could also be something in the rtl930x.mk definition for the device:

define Device/xikestor_sks8300-8t
  SOC := rtl9303
  DEVICE_VENDOR := XikeStor
  DEVICE_MODEL := SKS8300-8T
  BLOCKSIZE := 64k
  KERNEL_SIZE := 8192k
  IMAGE_SIZE := 20480k
  IMAGE/sysupgrade.bin := pad-extra 256 | append-kernel | xikestor-nosimg | \
  jffs2 nos.img -e 4KiB -x lzma | pad-to $$$$(KERNEL_SIZE) | \
  append-rootfs | pad-rootfs | append-metadata | check-size
endef
TARGET_DEVICES += xikestor_sks8300-8t

Any insight or suggestions would be greatly appreciated!

 QRTL9300# # flshow
=============== FLASH Partition Layout ===============
Index  Name       Size       Address
------------------------------------------------------
 0     LOADER     0x1c0000   0xb4000000-0xb41bffff
 1     BDINFO     0x10000    0xb41c0000-0xb41cffff
 2     SYSINFO    0x10000    0xb41d0000-0xb41dffff
 3     JFFS2_FACTORYDATA 0x10000    0xb41e0000-0xb41effff
 4     JFFS2_SYSDATA 0x10000    0xb41f0000-0xb41fffff
 5     JFFS2_FILESYSTEM 0xa00000   0xb4200000-0xb4bfffff
 6     RUNTIME1   0x1400000  0xb4c00000-0xb5ffffff
 7     RUNTIME2   0x0        0xb4000000-0xb3ffffff
======================================================

From the stock firmware

/www_wayos # cat /proc/mtd
dev:    size   erasesize  name
mtd0: 001c0000 00010000 "LOADER"
mtd1: 00010000 00010000 "BDINFO"
mtd2: 00010000 00010000 "SYSINFO"
mtd3: 00010000 00010000 "JFFS2 FACTORYDATA"
mtd4: 00010000 00010000 "JFFS2 SYSDATA"
mtd5: 00a00000 00010000 "JFFS2 FILESYSTEM"
mtd6: 01400000 00010000 "RUNTIME"
mtd7: 02000000 00010000 "RUNTIME2"

@musashino -san γŠι‘˜γ„γ—γΎγ™

You cannot use the U-Boot environment to figure out flash layout. It's just some arbitrary RTK default. This is what it looks like on the Onti ONT-S508CL-8S (identical to. XikeStor SKS8300-8X):

RTL9300# flshow
=============== FLASH Partition Layout ===============
Index  Name       Size       Address
------------------------------------------------------
 0     LOADER     0xe0000    0xb4000000-0xb40dffff
 1     BDINFO     0x10000    0xb40e0000-0xb40effff
 2     SYSINFO    0x10000    0xb40f0000-0xb40fffff
 3     JFFS2_CFG  0x100000   0xb4100000-0xb41fffff
 4     JFFS2_LOG  0x100000   0xb4200000-0xb42fffff
 5     RUNTIME1   0xe80000   0xb4300000-0xb517ffff
 6     RUNTIME2   0xe80000   0xb5180000-0xb5ffffff
======================================================

Notice that it doesn't match the DTS at all.

You need the layout from the stock firmware. A bold guess would be that it is similar to the SKS8300-8X, but checking is probably easier than guessing....

EDIT: The real layout is found in the mtdparts variable:

RTL9300# printenv mtdparts
mtdparts=mtdparts=spi_nor:30M@2M(flash_user),832K@1216K(sys_log),192K@1M(raw),1M@0(bootrom);

I think the hardware isn't exactly the same. I did try to compile my image based on the SKS8300-8X DTS config but it didn't work.

mtdparts is not defined in the U-Boot environment variables.

QRTL9300# # printenv mtdparts
## Error: "mtdparts" not defined
RTL9300# # printenv         
baudrate=115200
boardmodel=RTL9303_8X8261BE_V1
bootcmd=boota
bootdelay=3
ethact=rtl9300#0
ethaddr=00:E0:4C:00:00:00
fileaddr=81000000
filesize=B30621
ipaddr=10.42.0.2
ledModeInitSkip=0
serverip=10.42.0.1
stderr=serial
stdin=serial
stdout=serial

Environment size: 274/65532 bytes

In the stock firmware I am getting:

/www_wayos # cat /proc/mtd
dev:    size   erasesize  name
mtd0: 001c0000 00010000 "LOADER"
mtd1: 00010000 00010000 "BDINFO"
mtd2: 00010000 00010000 "SYSINFO"
mtd3: 00010000 00010000 "JFFS2 FACTORYDATA"
mtd4: 00010000 00010000 "JFFS2 SYSDATA"
mtd5: 00a00000 00010000 "JFFS2 FILESYSTEM"
mtd6: 01400000 00010000 "RUNTIME"
mtd7: 02000000 00010000 "RUNTIME2"

Quick update, I'm now trying to re-pack the sysupgrade firmware so it boots under U-Boot but face a validation issue. The stock firmware has an 80-byte header followed by LZMA-compressed content. My issue is when I modify any byte in the header, the device rejects it with "Bad Header Checksum", even after recalculating both CRC32 values correctly.

|---------------------------------| ← 0x00
|         HEADER                  |  80 bytes
|---------------------------------| ← 0x50
|       FIRMWARE CONTENT (FW)     | ← from 0x50 to EOF (this is lzma compressed firmware MIPS Linux Kernel Image (firmware_content.bin))
|---------------------------------| ← EOF

For V1.04.B01 (March 10, 2025):

Offset: 0x00-0x03  Value: 00 00 00 02 # Number of segments
Offset: 0x04-0x07  Value: 00 00 00 01 # Segment index to boot  
Offset: 0x08-0x0B  Value: 00 00 00 01 # Version/revision number
Offset: 0x0C-0x0F  Value: ce 65 af 08 # CRC32 checksum from offset 0x10 β†’ EOF CRC32(header from offset 0x10  + firmware_content.bin) (dd if=V1.04.B01\ Mar_10_2025.bin bs=1 skip=16 of=/tmp/tmp.bin && crc32 /tmp/tmp.bin)
Offset: 0x10-0x13  Value: 83 80 00 00 # Unknown value 1
Offset: 0x14-0x17  Value: 86 16 ba a9 # Unknown value 2
Offset: 0x18-0x1B  Value: 67 ce c6 1e # firmware timestamp hex(int("1741604382"))
Offset: 0x1C-0x1F  Value: 00 b3 05 d1 # len(firmware_content.bin) from offset 0x50
Offset: 0x20-0x23  Value: 80 00 00 00 # Load address - static
Offset: 0x24-0x27  Value: 80 38 74 00 # Entry point - static
Offset: 0x28-0x2B  Value: 81 01 2b d3 # crc32(firmware_content.bin) from offset 0x50 (dd if=V1.04.B01\ Mar_10_2025.bin bs=1 skip=80 of=/tmp/tmp.bin && crc32 /tmp/tmp.bin)
Offset: 0x2C-0x2F  Value: 05 05 02 03 # Version - static
Offset: 0x30-0x33  Value: 52 54 4b 5f (ASCII "RTK_")
Offset: 0x34-0x37  Value: 53 44 4b 00 (ASCII "SDK")
Offset: 0x38-0x50  # padding

For V1.04.B05 (July 25, 2025):

Offset: 0x00-0x03  Value: 00 00 00 02 # Number of segments
Offset: 0x04-0x07  Value: 00 00 00 01 # Segment index to boot  
Offset: 0x08-0x0B  Value: 00 00 00 01 # Version/revision number
Offset: 0x0C-0x0F  Value: 6c 50 5e e4 # CRC32 checksum from offset 0x10 β†’ EOF CRC32(header from offset 0x10  + firmware_content.bin) (dd if=V1.04.B05-20250725.bin bs=1 skip=16 of=/tmp/tmp.bin && crc32 /tmp/tmp.bin)
Offset: 0x10-0x13  Value: 93 00 00 00 # Unknown value 1
Offset: 0x14-0x17  Value: 66 39 12 01 # Unknown value 2
Offset: 0x18-0x1B  Value: 68 83 a6 fe # FW timestamp hex(int("1753458430"))
Offset: 0x1C-0x1F  Value: 00 b9 cc 84 # len(firmware_content.bin) from offset 0x50
Offset: 0x20-0x23  Value: 80 00 00 00 # Load address - static
Offset: 0x24-0x27  Value: 80 38 74 00 # Entry point - static
Offset: 0x28-0x2B  Value: 12 1c a3 58 # crc32(firmware_content.bin) from offset 0x50 (dd if=V1.04.B05-20250725.bin bs=1 skip=80 of=/tmp/tmp.bin && crc32 /tmp/tmp.bin)
Offset: 0x2C-0x2F  Value: 05 05 02 03 # Version - static
Offset: 0x30-0x33  Value: 52 54 4b 5f (ASCII "RTK_") - static
Offset: 0x34-0x37  Value: 53 44 4b 00 (ASCII "SDK") - static
Offset: 0x38-0x50  # padding

I believe the key is figuring out how unknown value 1 and unknown value 2 are generated. I'm currently reverse engineering the stock U-Boot but with nearly 80,000 lines of code, it's quite challenging. If anyone has any insights or suggestions, I'd greatly appreciate it!

Educated guess:

  • Byte 0x00-0x0F : Vendor header
  • Byte 0x10-0x50 : uimage header

So the recipe would be something like this

UIMAGE_MAGIC := 0x93000000 (or 0x83800000 no idea why vendor changed that)
KERNEL := kernel-bin | append-dtb | lzma | uImage lzma | vendor-stuff-here
1 Like

Yes you're probably right about bytes 0x10-0x13, they seem to correspond to the uimage magic (RTL8380 and RTL9300). Not sure why they changed since the last version and it confuses me a bit. Ok but let’s assume this part is also static. What about bytes 0x14-0x17? These need to be correct and I’m still unsure what they correspond to. Without the correct value for this, I will continue to get a "Bad Header Checksum" error...

1 Like

Like @plappermaul and @musashino says: The bytes from 0x10 to 0x50 looks like a complete and valid uimage header.

"Unknown value 2" is simply the crc32 checksum of this header, with the checksum field set to 0.

Note also that the value of your "Version" has a meaning and is sane:

os: 5 - Linux
arch: 5 - MIPS
type: 2 - OS Kernel Image
comp: 3 - lzma

Ref target/linux/generic/files/include/dt-bindings/mtd/partitions/uimage.h

1 Like

It worked! Many thanks to all of you for your assistance :slight_smile:

/*
 * xikestor-8t-header.c - Firmware Header Generator
 * Compatible devices:
 *   - XikeStor SKS8300-8T
 *   - XikeStor SKS8310-8X
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>

#define HEADER_SIZE         0x50
#define HARDWARE_ID         0x93000000
#define SEGMENTS            0x00000002
#define BOOT_SEGMENT        0x00000001
#define VERSION             0x00000001
#define DEFAULT_LOAD_ADDRESS    0x80000000
#define DEFAULT_ENTRY_POINT     0x80387400

/* Compression types */
#define COMP_NONE           0x00
#define COMP_GZIP           0x01
#define COMP_LZMA           0x03
#define DEFAULT_COMP_TYPE   COMP_LZMA

/* Image info base (0x05050200 + compression type) */
#define IMAGE_INFO_BASE     0x05050200

/* CRC32 table */
static uint32_t crc32_table[256];
static int crc32_table_initialized = 0;

/* Initialize CRC32 table */
static void init_crc32_table(void)
{
    uint32_t c;
    int n, k;
    
    for (n = 0; n < 256; n++) {
        c = (uint32_t)n;
        for (k = 0; k < 8; k++) {
            if (c & 1)
                c = 0xedb88320L ^ (c >> 1);
            else
                c = c >> 1;
        }
        crc32_table[n] = c;
    }
    crc32_table_initialized = 1;
}

/* Calculate CRC32 */
static uint32_t crc32(const uint8_t *buf, size_t len)
{
    uint32_t c = 0xffffffffL;
    size_t n;
    
    if (!crc32_table_initialized)
        init_crc32_table();
    
    for (n = 0; n < len; n++) {
        c = crc32_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
    }
    
    return c ^ 0xffffffffL;
}

/* Write 32-bit big-endian value */
static void write_be32(uint8_t *buf, uint32_t val)
{
    buf[0] = (val >> 24) & 0xff;
    buf[1] = (val >> 16) & 0xff;
    buf[2] = (val >> 8) & 0xff;
    buf[3] = val & 0xff;
}

/* Parse hex string to uint32_t */
static int parse_hex(const char *str, uint32_t *value)
{
    char *endptr;
    unsigned long val;
    
    /* Skip 0x prefix if present */
    if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
        str += 2;
    
    val = strtoul(str, &endptr, 16);
    
    if (*endptr != '\0' || val > UINT32_MAX) {
        return -1;
    }
    
    *value = (uint32_t)val;
    return 0;
}

/* Parse compression type */
static int parse_comp_type(const char *str, uint8_t *comp_type)
{
    if (strcasecmp(str, "none") == 0) {
        *comp_type = COMP_NONE;
    } else if (strcasecmp(str, "gzip") == 0) {
        *comp_type = COMP_GZIP;
    } else if (strcasecmp(str, "lzma") == 0) {
        *comp_type = COMP_LZMA;
    } else {
        return -1;
    }
    return 0;
}

static void usage(const char *prog)
{
    fprintf(stderr, "Usage: %s -i <input> -o <output> [-a <load_addr>] [-e <entry_point>] [-c <comp_type>]\n", prog);
    fprintf(stderr, "  -i <input>       Input firmware file\n");
    fprintf(stderr, "  -o <output>      Output firmware file with header\n");
    fprintf(stderr, "  -a <load_addr>   Load address in hex (default: 0x%08x)\n", DEFAULT_LOAD_ADDRESS);
    fprintf(stderr, "  -e <entry_point> Entry point in hex (default: 0x%08x)\n", DEFAULT_ENTRY_POINT);
    fprintf(stderr, "  -c <comp_type>   Compression type: none, gzip, lzma (default: lzma)\n");
    fprintf(stderr, "  -h               Show this help message\n");
}

int main(int argc, char *argv[])
{
    FILE *in_fp = NULL, *out_fp = NULL;
    uint8_t *firmware = NULL;
    uint8_t *complete_image = NULL;
    uint8_t header[HEADER_SIZE];
    size_t fw_size;
    uint32_t crc;
    uint32_t timestamp;
    uint32_t load_address = DEFAULT_LOAD_ADDRESS;
    uint32_t entry_point = DEFAULT_ENTRY_POINT;
    uint8_t comp_type = DEFAULT_COMP_TYPE;
    uint32_t image_info;
    char *input_file = NULL;
    char *output_file = NULL;
    int opt;
    int ret = 1;
    
    /* Parse command line */
    while ((opt = getopt(argc, argv, "i:o:a:e:c:h")) != -1) {
        switch (opt) {
        case 'i':
            input_file = optarg;
            break;
        case 'o':
            output_file = optarg;
            break;
        case 'a':
            if (parse_hex(optarg, &load_address) < 0) {
                fprintf(stderr, "Error: Invalid load address '%s'\n", optarg);
                usage(argv[0]);
                return 1;
            }
            break;
        case 'e':
            if (parse_hex(optarg, &entry_point) < 0) {
                fprintf(stderr, "Error: Invalid entry point '%s'\n", optarg);
                usage(argv[0]);
                return 1;
            }
            break;
        case 'c':
            if (parse_comp_type(optarg, &comp_type) < 0) {
                fprintf(stderr, "Error: Invalid compression type '%s'\n", optarg);
                fprintf(stderr, "Valid types: none, gzip, lzma\n");
                usage(argv[0]);
                return 1;
            }
            break;
        case 'h':
            usage(argv[0]);
            return 0;
        default:
            usage(argv[0]);
            return 1;
        }
    }
    
    if (!input_file || !output_file) {
        usage(argv[0]);
        return 1;
    }
    
    /* Open input file */
    in_fp = fopen(input_file, "rb");
    if (!in_fp) {
        fprintf(stderr, "Error: Cannot open input file '%s': %s\n", 
                input_file, strerror(errno));
        goto cleanup;
    }
    
    /* Get file size */
    fseek(in_fp, 0, SEEK_END);
    fw_size = ftell(in_fp);
    fseek(in_fp, 0, SEEK_SET);
    
    /* Allocate memory for firmware */
    firmware = malloc(fw_size);
    if (!firmware) {
        fprintf(stderr, "Error: Cannot allocate memory\n");
        goto cleanup;
    }
    
    /* Read firmware */
    if (fread(firmware, 1, fw_size, in_fp) != fw_size) {
        fprintf(stderr, "Error: Cannot read firmware\n");
        goto cleanup;
    }
    
    /* Initialize header with zeros */
    memset(header, 0, HEADER_SIZE);
    
    /* Fill header fields */
    write_be32(header + 0x00, SEGMENTS);
    write_be32(header + 0x04, BOOT_SEGMENT);
    write_be32(header + 0x08, VERSION);
    /* 0x0C: Header+FW CRC - calculated later */
    write_be32(header + 0x10, HARDWARE_ID);
    /* 0x14: Header self-CRC - calculated later */
    
    /* Use current timestamp */
    timestamp = time(NULL);
    write_be32(header + 0x18, timestamp);
    
    /* Firmware size */
    write_be32(header + 0x1C, fw_size);
    
    /* Load address and entry point */
    write_be32(header + 0x20, load_address);
    write_be32(header + 0x24, entry_point);
    
    /* 0x28: Firmware CRC - calculated next */
    
    /* Image info with compression type */
    image_info = IMAGE_INFO_BASE | comp_type;
    write_be32(header + 0x2C, image_info);
    
    /* RTK_SDK marker */
    memcpy(header + 0x30, "OpenWrt", 8);
    
    /* Step 1: Calculate firmware CRC */
    crc = crc32(firmware, fw_size);
    write_be32(header + 0x28, crc);
    
    /* Step 2: Calculate header self-CRC (with field zeroed) */
    memset(header + 0x14, 0, 4);
    crc = crc32(header + 0x10, 0x40);
    write_be32(header + 0x14, crc);
    
    /* Allocate memory for complete image */
    complete_image = malloc(HEADER_SIZE + fw_size);
    if (!complete_image) {
        fprintf(stderr, "Error: Cannot allocate memory\n");
        goto cleanup;
    }
    
    /* Combine header and firmware */
    memcpy(complete_image, header, HEADER_SIZE);
    memcpy(complete_image + HEADER_SIZE, firmware, fw_size);
    
    /* Step 3: Calculate header+firmware CRC */
    crc = crc32(complete_image + 0x10, HEADER_SIZE - 0x10 + fw_size);
    write_be32(complete_image + 0x0C, crc);
    
    /* Open output file */
    out_fp = fopen(output_file, "wb");
    if (!out_fp) {
        fprintf(stderr, "Error: Cannot create output file '%s': %s\n", 
                output_file, strerror(errno));
        goto cleanup;
    }
    
    /* Write complete image */
    if (fwrite(complete_image, 1, HEADER_SIZE + fw_size, out_fp) != 
        HEADER_SIZE + fw_size) {
        fprintf(stderr, "Error: Cannot write output file\n");
        goto cleanup;
    }
    
    /* Print summary */
    printf("Header generated successfully:\n");
    printf("  Input:       %s (%zu bytes)\n", input_file, fw_size);
    printf("  Output:      %s\n", output_file);
    printf("  Load addr:   0x%08x\n", load_address);
    printf("  Entry point: 0x%08x\n", entry_point);
    printf("  Compression: %s (0x%02x)\n", 
           comp_type == COMP_NONE ? "none" :
           comp_type == COMP_GZIP ? "gzip" :
           comp_type == COMP_LZMA ? "lzma" : "unknown",
           comp_type);
    printf("  Image info:  0x%08x\n", image_info);
    
    ret = 0;
    
cleanup:
    if (in_fp) fclose(in_fp);
    if (out_fp) fclose(out_fp);
    if (firmware) free(firmware);
    if (complete_image) free(complete_image);
    
    return ret;
}

The SKS8310-8X uses the same OEM firmware as the SKS8300-8T. Presumably wouldn't be too difficult to build on your work to get that supported, too.