Support for RTL838x based managed switches

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