Support for RTL838x based managed switches

I just looked through the U-Boot GPL dump for the DGS-1210-16 and the RTL8332 is treated there as a variant of the RTL8380. If the checks for the soc are adapted to also accept that chip, the code should simply work. The biggest work is likely to add support for the 2 external PHYs of the TPE, but I can see that the initialization code for both (8214b and 8208) can be found in the DGS GPL dump -- after all U-Boot needs to initialize the PHYs to allow you to do TFTP.

Birger

In the latest git, detection of the RTL8332 was added. Loading an image now should fail in a more interesting way than simply to refuse to work.
I can add PHY support for the 8214b and 8208 if you sends me the dmesg lines showing their probed device-ids. These PHYs exist in so many variants, I would like to add the right ones.

I won't be able to do much with this for a couple of weeks. Going out of town for a wedding, and then I'll need to replace the switch with another PoE swith I have before I can start playing with it's firmware. Also discovered a couple of D-Link switches in my collection DGS-1210-10P Ver A1 and a DGS-1210-24 Ver A1. The other TRENDnet is Marvell based, the Netgears all seem to be Broadcom and the TP-Link I haven't been able to determine what SOC it uses.

I actually also have a D-Link DGS-1210-10P which is currently hooked up to a logic analyzer in order to understand the PoE protocol. There is a Nuvoton ARM-Chip on the board which seems to monitor the Broadcom PoE chip using SPI. The RTL8380M on the Board then talks with the Nuvoton ARM SoC using a binary serial 19200 8N1 connection. The board even has 2 extra pin-headers: one to program the ARM chip using SWD, the other to listen in on the serial connection. There are only about 20 bytes sent every second (normally monitoring data which is always the same) in both directions but I am lacking inspiration what exactly could be exchanged...
I bet also the other D-Link has either an RTL8332 or RTL8382, the DGS-1210-16 I have has an RTL8382 and works already quite nicely with OpenWRT. It could have an RTL8390, though, which is in the same state of non-support as the RTL8332. Enjoy the wedding!

I managed to mostly decode the protocol for PoE for the DLink DGS-1210-10P. The following script does the trick:

#!/usr/bin/lua
local rs = require "luars232"

-- port_name = "/dev/ttyUSB0"
port_name = "/dev/ttyS1"

out = io.stderr
	
function initSerial(p)
	local e, p = rs.open(p)
	if e ~= rs.RS232_ERR_NOERROR then
		-- handle error
		out:write(string.format("can't open serial port '%s', error: '%s'\n",
				port_name, rs.error_tostring(e)))
		return
	end

	assert(p:set_baud_rate(rs.RS232_BAUD_19200) == rs.RS232_ERR_NOERROR)
	assert(p:set_data_bits(rs.RS232_DATA_8) == rs.RS232_ERR_NOERROR)
	assert(p:set_parity(rs.RS232_PARITY_NONE) == rs.RS232_ERR_NOERROR)
	assert(p:set_stop_bits(rs.RS232_STOP_1) == rs.RS232_ERR_NOERROR)
	assert(p:set_flow_control(rs.RS232_FLOW_OFF)  == rs.RS232_ERR_NOERROR)

	out:write(string.format("OK, port open with values '%s'\n", tostring(p)))

	return p
end

function receive(pCon)
--	local reply = {0x20, 0x01, 0x00, 0x08, 0x00, 0xe1, 0x21, 0x18, 0x01, 0x02, 0x21, 0x67}
	local reply = {}
	local retries = 0

	while table.getn(reply) < 12 and retries < 4 do
		-- Read up to 12 byte response, timeout 400ms
		err, data_read, size = pCon:read(12, 400)
		assert(err == rs.RS232_ERR_NOERROR)
--		io.write(string.format("-> [%2d]:", string.len(data_read)))
		for i = 1, string.len(data_read) do
			table.insert(reply, string.byte(string.sub(data_read, i, i)))
--			io.write(string.format(" %02x", reply[i]))
		end
--		io.write("\n")
		retries = retries + 1
	end
	if table.getn(reply) ~= 12 then
		return(nil)
	end
	local sum = 0
	for i = 1, 11 do
		sum = sum + reply[i]
	end
	if sum % 256 ~= reply[12] then
		print ("Checksum error!")
		return(nil)
	end
	return(reply)
end

function sendCommand(pCon, cmd)
	while table.getn(cmd) < 11 do
		table.insert(cmd, 0xff)
	end
	local sum = 0
	for i,v in ipairs(cmd) do
		sum = sum + v
	end
	table.insert(cmd, sum % 256)
	local c_string = ""
	for i = 1, 12 do
		c_string = c_string .. string.char(cmd[i])
	end
	err, len_written = pCon:write(c_string)
	assert(err == rs.RS232_ERR_NOERROR)

	local reply = receive(pCon)
	if reply then
		if (reply[1] == cmd[1] and reply[2] == cmd[2]) then
--			io.write("valid: ")
--			dumpReply(reply)
			return(reply)
		end
	end
	return(nil)
end

function dumpReply(reply)
	io.write("Reply: ")
	for i,v in ipairs(reply) do
		io.write(string.format(" %02x", v))
	end
	io.write("\n");
end
	
function getStatus(pCon)
	local cmd = {0x20, 0x01}
	local reply = sendCommand(pCon, cmd)
	if not reply then return(nil) end
	-- returns status, PoEExtVersion, PoEVersion, state2
	return({reply[5], reply[6], reply[7], reply[10]})
end

function disablePort(pCon, port)
	local cmd = {0x00, port, port, 0x00}
	-- disable command is always sent twice
	sendCommand(pCon, cmd)
	sendCommand(pCon, cmd)
end

function enablePort(pCon, port)
	local cmd = {0x00, port, port, 0x01}
	sendCommand(pCon, cmd)
end

function setPortRelPrio(pCon, port, prio)
	local cmd = {0x1d, 0x00, port, prio}
	sendCommand(pCon, cmd)
end

function setGlobalPowerBudget(pCon, maxPower, guard)
	-- maxPower and guard Watts
	local cmd = {0x18, 0x01, 0x00}
	table.insert(cmd, math.floor(maxPower * 10 / 256))
	table.insert(cmd, math.floor(maxPower * 10) % 256)
	table.insert(cmd, math.floor(guard * 10 / 256))
	table.insert(cmd, math.floor(guard * 10) % 256)
	sendCommand(pCon, cmd)
end

function setPowerLowAction(pCon, disableNext)
	local cmd = {0x17, 0x00}
	if disableNext then
		table.insert(cmd, 0x04)
	else
		table.insert(cmd, 0x02)
	end
	sendCommand(pCon, cmd)
end
	
function getPowerStat(pCon)
	local cmd = {0x23, 0x01}
	local reply = sendCommand(pCon, cmd)
	if not reply then return(nil) end
	local watts = (reply[3] * 256 + reply[4]) / 10.0
	return(watts)
end

function getPortPower(pCon, port)
	local cmd = {0x30, 0x01, port}
	local reply = sendCommand(pCon, cmd)
	if not reply then return(nil) end
	local watts = (reply[10] * 256 + reply[11]) / 10.0
	local mamps = reply[6] * 256 + reply[7]
	return({watts, mamps})
end

function getPortOverview(pCon)
	local cmd = {0x2a, 0x01, 0x00}
	local reply = sendCommand(pCon, cmd)
	if not reply then return(nil) end
	local s = { }
	for i = 4, 11 do
		if reply[i] == 0x10 then
			s[i-3] = "off"
		end
		if reply[i] == 0x11 then
			s[i-3] = "enabled"
		end 
		if reply[i] > 0x11 then
			s[i-3] = "active"
		end
	end
	return(s)
end

-- Priority for power: 3: High, 2: Normal, 1: Low?
function setPortPriority(pCon, port, prio)
	local cmd = {0x1a, port, port, prio}
	local reply = sendCommand(pCon, cmd)
	if not reply then return(nil) end
	return(unpack(reply, 4, 11))
end

function getPortPowerLimits(pCon, port)
	local cmd = {0x26, 0x01, port}
	local reply = sendCommand(pCon, cmd)
	if not reply then return(nil) end
	return(answer)
end

function startupPoE(pCon)
	local reply = nil
	reply = getStatus(pCon)
	
	
	setGlobalPowerBudget(pCon, 0, 0)
	setPowerLowAction(pCon, nil)
	-- do something unknown
	sendCommand(pCon, {0x06, 0x00, 0x01})
	for i = 0, 7 do
		disablePort(pCon, i)
	end
	-- do something unknown
	sendCommand(pCon, {0x02, 0x00, 0x01})

	for i = 0, 7 do
		disablePort(pCon, i)
	end
	-- do something unknown
	sendCommand(pCon, {0x02, 0x00, 0x01})

	for i = 0, 7 do
		setPortRelPrio(pCon, i, 7-i)
	end
	-- use monitor command 25
	sendCommand(pCon, {0x25, 0x01})
	
	setGlobalPowerBudget(pCon, 65.0, 7.0)
	getPowerStat(pCon)
	-- -> 23 01 00 00 02 44 00 02 ff ff 00 6a
	
	-- Set 4 unknown port properties:
	for i = 0, 7 do
		sendCommand(pCon, {0x11, i, i, 0x01})
		sendCommand(pCon, {0x13, i, i, 0x02})
		sendCommand(pCon, {0x15, i, i, 0x01})
		sendCommand(pCon, {0x10, i, i, 0x03})
	end
	for i = 0, 7 do
		enablePort(pCon, i)
	end

end	

local p = initSerial(port_name)
-- startupPoE(p)
local s = { }
while 1 do
	reply = getStatus(p)
--	dumpReply(reply)
	io.write(string.format("Total power: %f W\n", getPowerStat(p)))
	s = getPortOverview(p)
	io.write("Port state: ")
	for i = 1, 8 do
		io.write(string.format("%d: %s  ", i, s[i]))
	end
	io.write("\n")
	for i = 1, 8 do
		if s[i] == "active" then
			local r = getPortPower(p, i-1)
			io.write(string.format("Port %d: %f Watt, %d mA\n", i, r[1], r[2]))
		end
	end
	io.write("\n")
	os.execute("sleep " .. tonumber(2))
end

It gives the following result when first turning on all ports and then plugging a small switch into the first Ethernet port:

root@OpenWrt:/tmp# cat /proc/cpuinfo 
system type             : RTL8380
machine                 : D-Link DGS-1210-10p
processor               : 0
cpu model               : MIPS 4KEc V7.0
BogoMIPS                : 498.89
wait instruction        : yes
microsecond timers      : yes
tlb_entries             : 32
extra interrupt vector  : yes
hardware watchpoint     : yes, count: 2, address/irw mask: [0x0fff, 0x0fff]
isa                     : mips1 mips2 mips32r1 mips32r2 mips64r1 mips64r2
ASEs implemented        : mips16
Options implemented     : tlb 4kex 4k_cache 32fpr prefetch mcheck ejtag llsc dc_aliases perf_cntr_intr_bit
shadow register sets    : 1
kscratch registers      : 0
package                 : 0
core                    : 0
VCED exceptions         : not available
VCEI exceptions         : not available

root@OpenWrt:/tmp# ./serial.lua 
OK, port open with values 'device: /dev/ttyS1, baud: 19200, data bits: 8, parity: none, stop bits: 1, flow control: off'
Total power: 0.300000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 0.300000 Watt, 6 mA

Total power: 0.300000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 0.300000 Watt, 6 mA

Total power: 0.300000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 0.300000 Watt, 6 mA

Total power: 0.300000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 0.300000 Watt, 6 mA

Total power: 0.300000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 0.300000 Watt, 6 mA

Total power: 1.600000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 1.900000 Watt, 36 mA

Total power: 2.500000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.500000 Watt, 47 mA

Total power: 2.300000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.200000 Watt, 42 mA

Total power: 2.600000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.600000 Watt, 50 mA

Total power: 2.600000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.600000 Watt, 49 mA

Total power: 2.700000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.700000 Watt, 52 mA

Total power: 2.700000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.700000 Watt, 52 mA

Total power: 2.400000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.400000 Watt, 46 mA

Total power: 2.400000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.700000 Watt, 52 mA

Total power: 2.600000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.700000 Watt, 52 mA

Total power: 3.000000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.900000 Watt, 55 mA

Total power: 3.000000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 3.000000 Watt, 57 mA

Total power: 3.000000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.800000 Watt, 53 mA

Total power: 2.900000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.900000 Watt, 55 mA

Total power: 2.900000 W
Port state: 1: active  2: enabled  3: enabled  4: enabled  5: enabled  6: enabled  7: enabled  8: enabled  
Port 1: 2.900000 Watt, 54 mA

Terminated

1 Like

Not an easy to find one, indeed, but some google hacking can get you there :wink: https://manualzz.com/doc/31627850/draft-datasheet

If you want the PDF drop me an email (this username at klondike.es) and I'll send it to you :slight_smile:

1 Like

Thanks for the link. I managed to download the .pdf. Very useful to understand the background!
From the latest GPL drops and from
https://www.realtek.com/en/products/communications-network-ics/category/managed-switch-controller
one can see that there are more recent additions to the family, apart from the RTL839x there is now an RTL930x and RTL931x family targeting small office and ambitious home users with mult-gigabit switches. Have you by any chance see any datasheets of those?

I haven't found any datasheets for these yet, sorry.

Do you know if anybody has tried to reach Realtek about this? They may be the first ones interested into getting Linux support for their own products.

I have not contacted Realtek myself and am not aware of anyone having done that so far. My impression is that Realtek is very secretive about what they do. Having those datasheets would be very cool, however, especially to understand the advanced L2 and L3 features.
For the 838x and 839x the situation is not so bad because there are u-boot GPL dumps which cover basic network drivers, all the code to set the PHYs up and code to work with the GPIOs and flash. Since basically every switch manufacturer from Allnet to Zyxel has these devices in their lines of web-managed switches and the hardware configurations are all based on the same demo-boards, it is possible to stitch things together. What is mostly missing, and in clear violation of the GPL, are the Linux adaptations for these chips. For example the GPL dump for the Netgear GS728TPv2 shows a lot of hints about MIPS architecture adaptations and USB drivers but all interesting files are left out. My impression is that their understanding of the GPL is that if you can put it into a separate source file, you don't need to distribute the source code :wink:
For the 9300 and 9310 there is absolutely no u-boot GPL code apart from register names, although I am sure u-boot always gets linked together into one big binary blob. Supporting these devices in OpenWRT could be interesting, since they cover the range of managed multi-gig switches people now start putting into their basements to have the Ethernet bandwidth for WiFi Wave 6 devices.

Well I have written to Realtek asking for the datasheets for the RTL838x. Let's start there and see where this goes. In my experience the hardest step is usually making the initial contact.

That is great! I hope this works, it would be really cool to be able to refer to the datasheets. Thanks for the initiative.

cisco sg-250-26@marvel-armada ... has some promise... dts... serial seems read only as best I can tell... :frowning:

my 2 test samples arrived today :smiley:

1 Like

so the dlink uses a serial/tty and the netgear a usb poe driver ? is that the prolific usb chipset ? can we read power consumption and random other data ?
and is the poe passive/active 24/48V ? we could probably add a driver to drive this via the iio layer, never touched it. using the regulator sub system might also be an option
opening the dlink case i saw a cable to the top casing, i assume this is a shift register cascade to drive the leds ?
John

All chips use serial/tty to read out f/control PoE. We can read out power consumption and other data and set e.g. power limits per port. All brands use variations of the same binary protocol, which roughly looks like this:

Binary 12-byte sequence
cc sq dd dd FF FF FF FF FF FF FF cs
cc: command
sq: sequence number
dd: command options
FF: 0xFF for commands that do not need all 12 bytes
cs: checksum: LSB of sum over all bytes in command

Everything I know about the D-Link PoE steering is in


PoE is active 48V.
Yes, shift register cascade. Normally hardware controlled by the SoC, look for serial LEDs in the manual posted by kondike above.
Example for software control is

but normally the Bootloader already initializes the LEDs to be SoC controlled.
Birger

Great work!

I have collected some leaked documents about realtek chips on the Chinese website, some of which are related to rtl838x (sdk and uboot instructions, L2 api documents, etc.). They are on my telegram channel, @Realtek_Switch_Hacking, maybe it will help.

1 Like

That is great stuff! This will take some time to dig through, but this really amazing: I already looked at the datasheets for the RTL8231 and the RTL9301 and this invaluable. Thanks so much!

Birger

I just added Support for the ALLNET ALL-SG8208M switch: 8-port GE, 128MB RAM, 16MB flash, approx 70Euros incl. shipping, code in the github repository above.
LEDs and reset switch are supported. Installation can be done by flashing the sysupgrade image via the OEM web interface. The switch has an already soldered on serial header (with markings), a hard on/off switch and in addition to the front software reset switch a hard-wired reset switch hidden at the right behind the air-vents. Power is via external 12V power supply with standard barrel connector.

Birger

5 Likes

These are great news, thanks and congrats.

i've got one of these front panel PCB's from a dlink DGS-1100-16 v1 ( smallflash 2MB )...

it's got a 10 pin ribbon and two rtl square chips on it with hard to read names ( but when I searched all that came up were switch chips ) which i'd assume are the shift reg's with 2+ pins power and 2? pins for the button...

does this sound the same as the one/ones you've been working with?

if so... would you have a pinout / pointers to get the driver up and running using GPIO on another board ( i see the dts &led_sys definitions so probably just the higher level kmod / references i'm after )?

( i.e. is there 2 dedicated pins CLK/DATA per chip from the ribbon? assuming they are not daisy chained - 3.3v?)