diff mbox series

[v2] rtl83xx-poe: add package

Message ID 20210313165419.10713-1-bjorn@mork.no
State Superseded
Delegated to: Petr Štetiar
Headers show
Series [v2] rtl83xx-poe: add package | expand

Commit Message

Bjørn Mork March 13, 2021, 4:54 p.m. UTC
From: John Crispin <john@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@gs1900-10hp:~# ubus -v list poe
'poe' @3c3a28fb
        "info":{}
        "port":{"enable":"Boolean","port":"Integer"}
root@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@birger-koblitz.de>
Signed-off-by: John Crispin <john@phrozen.org>
Signed-off-by: Bjørn Mork <bjorn@mork.no> [commit message, release number]
---
"Adrian Schmutzler" <mail@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

Comments

Stijn Segers March 13, 2021, 5:25 p.m. UTC | #1
"Bjørn Mork" <bjorn@mork.no> schreef op 13 maart 2021 17:54:19 CET:
>From: John Crispin <john@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@gs1900-10hp:~# ubus -v list poe
>'poe' @3c3a28fb
>        "info":{}
>        "port":{"enable":"Boolean","port":"Integer"}
>root@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@birger-koblitz.de>
>Signed-off-by: John Crispin <john@phrozen.org>
>Signed-off-by: Bjørn Mork <bjorn@mork.no> [commit message, release number]

Tested-by: Stijn Segers <foss@volatilesystems.org>
>---
>"Adrian Schmutzler" <mail@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
>
>
>_______________________________________________
>openwrt-devel mailing list
>openwrt-devel@lists.openwrt.org
>https://lists.openwrt.org/mailman/listinfo/openwrt-devel
Rafał Miłecki April 14, 2021, 5:21 a.m. UTC | #2
On 13.03.2021 17:54, Bjørn Mork wrote:
> From: John Crispin <john@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@gs1900-10hp:~# ubus -v list poe
> 'poe' @3c3a28fb
>          "info":{}
>          "port":{"enable":"Boolean","port":"Integer"}
> root@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@birger-koblitz.de>
> Signed-off-by: John Crispin <john@phrozen.org>
> Signed-off-by: Bjørn Mork <bjorn@mork.no> [commit message, release number]

This is a rather hacky way to support one particular device rather than
a proper solution for PoE capable devices. I really think we should
design a proper PoE layer instead of accepting this hack.

I'm sorry but I'm really against commiting this as it is. It supports
rtl83xx without any standarized way. It adds non-generic ubus object
with a generic "poe" name. It opens doors for more unstandarized
solutions.

Because of above:
Nacked-by: Rafał Miłecki <rafal@milecki.pl>

Now how about to solve it.


Regarding UCI syntax. One idea is to have section for PoE /device/ and
sections for each of its ports. That way we can also support more
complex devices, e.g. those with more port options. I think there may be
hw allowing e.g. setting max port power budget or port priority.

Example /etc/config/poe:

config hw rtl83xx
	option protocol rtl83xx
	option budget 65

config port
	option poe rtl83xx
	option index 0
	option enabled 0

config port
	option poe rtl83xx
	option index 1
	option enabled 1
	option priority 3	# <- example for further devices

I'm still wondering if a separated config file (/etc/config/poe) is a
correct solution. After all we describe swithes in the
/etc/config/network . Maybe we can just extend that?


Then ubus + reading PoE status. I think we should support:
1. More than 1 PoE device (so e.g. poe.rtl83xx, poe.foo, poe.bar)
2. Have more generic ports syntax. 0W doesn't tell me if power usage is
    so low or if port is disabled

Maybe somethig like:

# ubus call poe.rtl83xx info
{
	"power_budget": "77 W",
	"power_consumption": "7.8 W",
	"ports": [
		{
			"index": 0,
			"enabled": 0
		},
		{
			"index": 0,
			"enabled": 1,
			"current_power": "7 W"
		}
	]
}


Design I described above should be generic enough & allow e.g. adding
LuCI support for handling PoE devices cleanly.


Finally I think we could pick a better language than Lua for
implementing rtl83xx driver.
Petr Štetiar April 14, 2021, 8:34 a.m. UTC | #3
Rafał Miłecki <zajec5@gmail.com> [2021-04-14 07:21:44]:

Hi,

> Finally I think we could pick a better language than Lua for
> implementing rtl83xx driver.

FYI https://www.kernel.org/doc/Documentation/hwmon/tps23861.rst

-- ynezz
diff mbox series

Patch

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
+}