[PATCH v2] rtl83xx-poe: add package

Bjørn Mork bjorn at mork.no
Sat Mar 13 16:54:19 GMT 2021


From: John Crispin <john at phrozen.org>

This package implements the microcontroller protocol used to
talk Broadcom PSE controllers on a number of realtek switches.
It is required to enable PoE ouput on supported hardware.

The implemented ABI allows individial control and monitoring
of each PoE port using ubus.  Example from a ZyXEL GS1900-10HP:

root at gs1900-10hp:~# ubus -v list poe
'poe' @3c3a28fb
        "info":{}
        "port":{"enable":"Boolean","port":"Integer"}
root at gs1900-10hp:~# ubus call poe info
{
        "ports": [
                "enabled",
                "enabled",
                "0W",
                "enabled",
                "enabled",
                "enabled",
                "4.6W",
                "4W"
        ],
        "power_budget": "77W",
        "power_consumption": "7.8W"
}

Tested-by: Birger Koblitz <mail at birger-koblitz.de>
Signed-off-by: John Crispin <john at phrozen.org>
Signed-off-by: Bjørn Mork <bjorn at mork.no> [commit message, release number]
---
"Adrian Schmutzler" <mail at adrianschmutzler.de> writes:

> Is this needed in core repo?

I believe it is.  This package (or another implementation of the protocol) is
required to turn on the PoE hardware on a number of realtek switches.  I must
admit that I'm not completely sure about the policies wrt core vs packages,
but my understanding is that hardware enabling packages belong in core.

Will follow-up with a patch adding this to DEVICE_PACKAGES of the affected
hardware, replacing the current lua-rs232 dependency (which is really this
package).


Bjørn

 package/rtl83xx-poe/Makefile             |  29 +++
 package/rtl83xx-poe/files/bin/poe.lua    | 316 +++++++++++++++++++++++
 package/rtl83xx-poe/files/etc/config/poe |  10 +
 package/rtl83xx-poe/files/etc/init.d/poe |  18 ++
 4 files changed, 373 insertions(+)
 create mode 100644 package/rtl83xx-poe/Makefile
 create mode 100755 package/rtl83xx-poe/files/bin/poe.lua
 create mode 100644 package/rtl83xx-poe/files/etc/config/poe
 create mode 100755 package/rtl83xx-poe/files/etc/init.d/poe

diff --git a/package/rtl83xx-poe/Makefile b/package/rtl83xx-poe/Makefile
new file mode 100644
index 000000000000..226e6ce694c4
--- /dev/null
+++ b/package/rtl83xx-poe/Makefile
@@ -0,0 +1,29 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=rtl83xx-poe
+PKG_RELEASE:=1
+
+PKG_LICENSE:=GPL-2.0-or-later
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/rtl83xx-poe
+  SECTION:=utils
+  CATEGORY:=Utilities
+  DEPENDS:=+libubox-lua +libubus-lua +libuci-lua +lua-rs232
+  TITLE:=PoE daemon for realtek switches
+endef
+
+define Package/rtl83xx-poe/description
+ This package contains an utility to allow triggering the PoE state of realtek switch ports.
+endef
+
+define Build/Compile
+
+endef
+
+define Package/rtl83xx-poe/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,rtl83xx-poe))
diff --git a/package/rtl83xx-poe/files/bin/poe.lua b/package/rtl83xx-poe/files/bin/poe.lua
new file mode 100755
index 000000000000..86dafe13cd01
--- /dev/null
+++ b/package/rtl83xx-poe/files/bin/poe.lua
@@ -0,0 +1,316 @@
+#!/usr/bin/lua
+local rs = require "luars232"
+
+port_name = "/dev/ttyS1"
+out = io.stderr
+nseq = 0
+
+budget = 65.0
+port_power = {0, 0, 0, 0, 0, 0, 0, 0 }
+
+if arg[1] ~= nil then
+	budget = tonumber(arg[1])
+end
+for i = 1, 8 do
+	port_power[i] = arg[i + 1]
+end
+
+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 = {}
+	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
+		print ("Unexpected length!")
+		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)
+	nseq = nseq + 1
+	cmd[2] = nseq % 256
+
+	while table.getn(cmd) < 11 do
+		table.insert(cmd, 0xff)
+	end
+	local c_string = ""
+	local sum = 0
+--	io.write("send  ")
+	for i = 1, 11 do
+		sum = sum + cmd[i]
+--		io.write(string.format(" %02x", cmd[i]))
+		c_string = c_string .. string.char(cmd[i])
+	end
+--	io.write(string.format(" %02x\n", sum % 256))
+	c_string = c_string .. string.char(sum % 256)
+	err, len_written = pCon:write(c_string)
+	assert(err == rs.RS232_ERR_NOERROR)
+
+	local reply = receive(pCon)
+	if reply then
+--		io.write("recv  ")
+--		dumpReply(reply)
+		if (reply[1] == cmd[1] and reply[2] == cmd[2]) then
+			return(reply)
+		else
+			if reply[1] == 0xfd then
+				print ("An incomplete request was received!")
+			elseif reply[1] == 0xfe then
+				print ("Request frame checksum was incorrect!")
+			elseif reply[1] == 0xff then
+				print ("Controller was not ready to respond !")
+			else
+				print ("Sequence number mismatch!")
+			end
+		end
+	else
+		print ("Missing reply!")
+	end
+	return(nil)
+end
+
+function dumpReply(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"
+		elseif reply[i] == 0x11 then
+			s[i-3] = "enabled"
+		elseif reply[i] > 0x11 then
+			s[i-3] = "active"
+		else
+			s[i-3] = "unknown"
+		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(reply)
+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
+		if port_power[i + 1] ~= "1" then
+			disablePort(pCon, i)
+		end
+	end
+	-- do something unknown
+	sendCommand(pCon, {0x02, 0x00, 0x01})
+
+	for i = 0, 7 do
+		if port_power[i + 1] ~= "1" then
+			disablePort(pCon, i)
+		end
+	end
+	-- do something unknown
+	sendCommand(pCon, {0x02, 0x00, 0x01})
+
+	-- 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
+		if port_power[i + 1] == "1" then
+			enablePort(pCon, i)
+		end
+	end
+
+end
+
+local p = initSerial(port_name)
+startupPoE(p)
+
+require "ubus"
+require "uloop"
+
+uloop.init()
+
+local conn = ubus.connect()
+if not conn then
+        error("Failed to connect to ubus")
+end
+
+local my_method = {
+	poe = {
+		info = {
+			function(req, msg)
+				local reply = {}
+
+				reply.power_consumption = tostring(getPowerStat(p)).."W"
+				reply.power_budget = tostring(budget).."W"
+
+				reply.ports = {}
+				local s = getPortOverview(p)
+				for i = 1, 8 do
+					if s[i] == "active" then
+						local r = getPortPower(p, i - 1)
+						reply.ports[i] = tostring(r[1]).."W"
+					else
+						reply.ports[i] = s[i]
+					end
+				end
+				conn:reply(req, reply);
+			end, {}
+		},
+		port = {
+			function(req, msg)
+				local reply = {}
+				if msg.port < 1 or msg.port > 8 then
+					conn:reply(req, false);
+					return -1
+				end
+				if msg.enable == true then
+					enablePort(p, msg.port - 1)
+				else
+					disablePort(p, msg.port - 1)
+				end
+				conn:reply(req, reply);
+			end, {port = ubus.INT32, enable = ubus.BOOLEAN }
+		},
+	},
+}
+
+conn:add(my_method)
+
+uloop.run()
diff --git a/package/rtl83xx-poe/files/etc/config/poe b/package/rtl83xx-poe/files/etc/config/poe
new file mode 100644
index 000000000000..4fc9723c88c7
--- /dev/null
+++ b/package/rtl83xx-poe/files/etc/config/poe
@@ -0,0 +1,10 @@
+config poe poe
+	option budget	65
+	option port1	0
+	option port2	0
+	option port3	0
+	option port4	0
+	option port5	0
+	option port6	0
+	option port7	0
+	option port8	0
diff --git a/package/rtl83xx-poe/files/etc/init.d/poe b/package/rtl83xx-poe/files/etc/init.d/poe
new file mode 100755
index 000000000000..159340b03a38
--- /dev/null
+++ b/package/rtl83xx-poe/files/etc/init.d/poe
@@ -0,0 +1,18 @@
+#!/bin/sh /etc/rc.common
+START=40
+
+USE_PROCD=1
+PROG=/bin/poe.lua
+
+start_service() {
+	local budget=$(uci get poe.poe.budget)
+
+	procd_open_instance
+	procd_set_param command "$PROG"
+	procd_append_param command ${budget:-65}
+	for p in `seq 1 8`; do
+		local pwr=$(uci get poe.poe.port$p)
+		procd_append_param command  ${pwr:-0}
+	done
+	procd_close_instance
+}
-- 
2.20.1




More information about the openwrt-devel mailing list