From patchwork Tue May 11 15:22:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: John Crispin X-Patchwork-Id: 1477203 X-Patchwork-Delegate: blogic@openwrt.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=lists.openwrt.org (client-ip=2001:8b0:10b:1:d65d:64ff:fe57:4e05; helo=desiato.infradead.org; envelope-from=openwrt-devel-bounces+incoming=patchwork.ozlabs.org@lists.openwrt.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; secure) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=desiato.20200630 header.b=ePws3x4F; dkim-atps=neutral Received: from desiato.infradead.org (desiato.infradead.org [IPv6:2001:8b0:10b:1:d65d:64ff:fe57:4e05]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4FfhZ64sBQz9sVt for ; Wed, 12 May 2021 01:25:18 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding :Content-Type:List-Subscribe:List-Help:List-Post:List-Archive: List-Unsubscribe:List-Id:MIME-Version:Message-Id:Date:Subject:Cc:To:From: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:List-Owner; bh=cST8Yd5YuZEGjMnkxuaAHP9HEH315XOkThgzw3fZi4k=; b=ePws3x4FMNp5uj0CzFBBWC2Clt Dfs1QUhfbBycXIrrVPYwKF+Bi0Cdq1SQJ/dNyx18R2R/AOoZvauXl77orwHrKFdl0LcRBG0a0v4/Z 12usEh8tNKNVmlTnNU8A5Gi28t4RMxsixHIeePaCRgVa+dxrzi9dQofS1EJRmd5rf3In9o3fEjTiV CUbJPVMJhWc0VJlUoJHLTCdAYUIyNoWO8QHVkJFJF5DwOyC06q+JZeu1+3G6HvAGWszJbCnCSfiuC nivPhL9RKVHxgewG65128NhP0wWxTMm7PZiQGDZcrHmeYFe9vssppPAyqEdZeK0TjJZzZERdSxAzY TootBb5w==; Received: from localhost ([::1] helo=desiato.infradead.org) by desiato.infradead.org with esmtp (Exim 4.94 #2 (Red Hat Linux)) id 1lgUDt-000avg-Fe; Tue, 11 May 2021 15:23:01 +0000 Received: from nbd.name ([2a01:4f8:221:3d45::2]) by desiato.infradead.org with esmtps (Exim 4.94 #2 (Red Hat Linux)) id 1lgUDn-000auP-Nf for openwrt-devel@lists.openwrt.org; Tue, 11 May 2021 15:22:58 +0000 Received: from 149.233.129.47.dynamic-pppoe.dt.ipv4.wtnet.de ([149.233.129.47] helo=bertha9.fritz.box) by ds12 with esmtpa (Exim 4.89) (envelope-from ) id 1lgUDk-0003L1-Hl; Tue, 11 May 2021 17:22:52 +0200 From: John Crispin To: openwrt-devel@lists.openwrt.org Cc: John Crispin Subject: [PATCH] realtek-poe: add support for PoE on Realtek switches Date: Tue, 11 May 2021 17:22:43 +0200 Message-Id: <20210511152243.1167160-1-john@phrozen.org> X-Mailer: git-send-email 2.25.1 MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20210511_162256_030221_54FA850C X-CRM114-Status: GOOD ( 24.02 ) X-Spam-Score: 0.0 (/) X-Spam-Report: Spam detection software, running on the system "desiato.infradead.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: This patch adds a C rewrite of the LUA tool previously posted. With this new daemon we map the DSA port names to the PSE port id. Support for several now features was added, such as setting the priori [...] Content analysis details: (0.0 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record X-BeenThere: openwrt-devel@lists.openwrt.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: OpenWrt Development List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "openwrt-devel" Errors-To: openwrt-devel-bounces+incoming=patchwork.ozlabs.org@lists.openwrt.org This patch adds a C rewrite of the LUA tool previously posted. With this new daemon we map the DSA port names to the PSE port id. Support for several now features was added, such as setting the priority and at/af mode. In the next iteration addition port/mcu states will be exposed, aswell as adding support for 4 wire mode and fault handling. A Sample config looks like this: config global option budget '65' config port option enable '1' option id '1' option name 'lan1' option poe_plus '1' option priority '2' ubus call poe info { "firmware": "v48.2", "mcu": "Nuvoton M05xx LAN Microcontroller", "budget": 65.000000, "consumption": 2.400000, "ports": { "lan1": { "priority": 2, "mode": "PoE", "status": "Delivering power", "consumption": 2.400000 } } } Signed-off-by: John Crispin Tested-by: Bjørn Mork --- package/network/config/realtek-poe/Makefile | 25 + .../config/realtek-poe/files/etc/config/poe | 9 + .../config/realtek-poe/files/etc/init.d/poe | 20 + .../config/realtek-poe/src/CMakeLists.txt | 28 + package/network/config/realtek-poe/src/main.c | 844 ++++++++++++++++++ 5 files changed, 926 insertions(+) create mode 100644 package/network/config/realtek-poe/Makefile create mode 100644 package/network/config/realtek-poe/files/etc/config/poe create mode 100755 package/network/config/realtek-poe/files/etc/init.d/poe create mode 100644 package/network/config/realtek-poe/src/CMakeLists.txt create mode 100644 package/network/config/realtek-poe/src/main.c diff --git a/package/network/config/realtek-poe/Makefile b/package/network/config/realtek-poe/Makefile new file mode 100644 index 0000000000..4dba054bb2 --- /dev/null +++ b/package/network/config/realtek-poe/Makefile @@ -0,0 +1,25 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=realtek-poe +PKG_RELEASE:=1 + +PKG_LICENSE:=GPL-2.0 +PKG_MAINTAINER:=John Crispin + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +define Package/realtek-poe + SECTION:=net + CATEGORY:=Network + TITLE:=Realtek PoE Switch Port daemon + DEPENDS:=@TARGET_realtek +libubox +libubus +libuci +endef + +define Package/realtek-poe/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/realtek-poe $(1)/usr/bin/ + $(CP) ./files/* $(1) +endef + +$(eval $(call BuildPackage,realtek-poe)) diff --git a/package/network/config/realtek-poe/files/etc/config/poe b/package/network/config/realtek-poe/files/etc/config/poe new file mode 100644 index 0000000000..6ef9d40ad2 --- /dev/null +++ b/package/network/config/realtek-poe/files/etc/config/poe @@ -0,0 +1,9 @@ +config global + option budget '65' + +config port + option enable '1' + option id '1' + option name 'lan1' + option poe_plus '1' + option priority '2' diff --git a/package/network/config/realtek-poe/files/etc/init.d/poe b/package/network/config/realtek-poe/files/etc/init.d/poe new file mode 100755 index 0000000000..66f77d6ef9 --- /dev/null +++ b/package/network/config/realtek-poe/files/etc/init.d/poe @@ -0,0 +1,20 @@ +#!/bin/sh /etc/rc.common + +START=80 +USE_PROCD=1 +PROG=/usr/bin/realtek-poe + +reload_service() { + ubus call poe reload +} + +service_triggers() { + procd_add_config_trigger "config.change" "poe" ubus call poe reload +} + +start_service() { + procd_open_instance + procd_set_param command "$PROG" + procd_set_param respawn + procd_close_instance +} diff --git a/package/network/config/realtek-poe/src/CMakeLists.txt b/package/network/config/realtek-poe/src/CMakeLists.txt new file mode 100644 index 0000000000..4eb81f4577 --- /dev/null +++ b/package/network/config/realtek-poe/src/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(realtek-poe C) +ADD_DEFINITIONS(-Os -ggdb -Wextra -Wall -Werror --std=gnu99 -Wmissing-declarations -Wno-unused-parameter) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +SET(SOURCES main.c) + +IF(DEBUG) + ADD_DEFINITIONS(-DDEBUG -g3) +ENDIF() + +FIND_LIBRARY(ubus NAMES ubus) +FIND_LIBRARY(uci NAMES uci) +FIND_LIBRARY(ubox NAMES ubox) +FIND_PATH(uci_include_dir NAMES uci.h) +FIND_PATH(ubus_include_dir NAMES libubus.h) +FIND_PATH(ubox_include_dir NAMES libubox/usock.h) +INCLUDE_DIRECTORIES(${ubox_include_dir} ${ubus_include_dir} ${uci_include_dir}) + +ADD_EXECUTABLE(realtek-poe ${SOURCES}) + +TARGET_LINK_LIBRARIES(realtek-poe ${ubox} ${ubus} ${uci}) + +INSTALL(TARGETS realtek-poe + RUNTIME DESTINATION sbin +) diff --git a/package/network/config/realtek-poe/src/main.c b/package/network/config/realtek-poe/src/main.c new file mode 100644 index 0000000000..034ac27432 --- /dev/null +++ b/package/network/config/realtek-poe/src/main.c @@ -0,0 +1,844 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define ULOG_DBG(fmt, ...) ulog(LOG_DEBUG, fmt, ## __VA_ARGS__) + +typedef int (*poe_reply_handler)(unsigned char *reply); + +#define MAX_PORT 8 +#define GET_STR(a, b) (a < ARRAY_SIZE(b) ? b[a] : NULL) + +struct port_config { + char name[16]; + unsigned char enable; + unsigned char priority; + unsigned char power_up_mode; + unsigned char power_budget; +}; + +struct config { + int debug; + + int budget; + int budget_guard; + + int port_count; + struct port_config ports[MAX_PORT]; +}; + +struct port_state { + char *status; + float watt; + char *poe_mode; +}; + +struct state { + char *sys_mode; + unsigned char sys_version; + char *sys_mcu; + char *sys_status; + unsigned char sys_ext_version; + float power_consumption; + + struct port_state ports[MAX_PORT]; +}; + +struct cmd { + struct list_head list; + unsigned char cmd[12]; +}; + +static struct uloop_timeout state_timeout; +static struct ubus_auto_conn conn; +static struct ustream_fd stream; +static LIST_HEAD(cmd_pending); +static unsigned char cmd_seq; +static struct state state; +static struct blob_buf b; + +static struct config config = { + .budget = 65, + .budget_guard = 7, + .port_count = 8, +}; + +static void +config_load_port(struct uci_section *s) +{ + enum { + PORT_ATTR_ID, + PORT_ATTR_NAME, + PORT_ATTR_ENABLE, + PORT_ATTR_PRIO, + PORT_ATTR_POE_PLUS, + __PORT_ATTR_MAX, + }; + + static const struct blobmsg_policy port_attrs[__PORT_ATTR_MAX] = { + [PORT_ATTR_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 }, + [PORT_ATTR_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, + [PORT_ATTR_ENABLE] = { .name = "enable", .type = BLOBMSG_TYPE_INT32 }, + [PORT_ATTR_PRIO] = { .name = "priority", .type = BLOBMSG_TYPE_INT32 }, + [PORT_ATTR_POE_PLUS] = { .name = "poe_plus", .type = BLOBMSG_TYPE_INT32 }, + }; + + const struct uci_blob_param_list port_attr_list = { + .n_params = __PORT_ATTR_MAX, + .params = port_attrs, + }; + + struct blob_attr *tb[__PORT_ATTR_MAX] = { 0 }; + unsigned int id; + + blob_buf_init(&b, 0); + uci_to_blob(&b, s, &port_attr_list); + blobmsg_parse(port_attrs, __PORT_ATTR_MAX, tb, blob_data(b.head), blob_len(b.head)); + + if (!tb[PORT_ATTR_ID] || !tb[PORT_ATTR_NAME]) { + ULOG_ERR("invalid port settings"); + return; + } + + id = blobmsg_get_u32(tb[PORT_ATTR_ID]); + if (!id || id > MAX_PORT) { + ULOG_ERR("invalid port id"); + return; + } + id--; + + strncpy(config.ports[id].name, blobmsg_get_string(tb[PORT_ATTR_NAME]), 16); + + if (tb[PORT_ATTR_ENABLE]) + config.ports[id].enable = !!blobmsg_get_u32(tb[PORT_ATTR_ENABLE]); + + if (tb[PORT_ATTR_PRIO]) + config.ports[id].priority = blobmsg_get_u32(tb[PORT_ATTR_PRIO]); + if (config.ports[id].priority > 3) + config.ports[id].priority = 3; + + if (tb[PORT_ATTR_POE_PLUS] && blobmsg_get_u32(tb[PORT_ATTR_POE_PLUS])) + config.ports[id].power_up_mode = 3; +} + +static void +config_load_global(struct uci_section *s) +{ + enum { + GLOBAL_ATTR_BUDGET, + GLOBAL_ATTR_GUARD, + __GLOBAL_ATTR_MAX, + }; + + static const struct blobmsg_policy global_attrs[__GLOBAL_ATTR_MAX] = { + [GLOBAL_ATTR_BUDGET] = { .name = "budget", .type = BLOBMSG_TYPE_INT32 }, + [GLOBAL_ATTR_GUARD] = { .name = "guard", .type = BLOBMSG_TYPE_INT32 }, + }; + + const struct uci_blob_param_list global_attr_list = { + .n_params = __GLOBAL_ATTR_MAX, + .params = global_attrs, + }; + + struct blob_attr *tb[__GLOBAL_ATTR_MAX] = { 0 }; + + blob_buf_init(&b, 0); + uci_to_blob(&b, s, &global_attr_list); + blobmsg_parse(global_attrs, __GLOBAL_ATTR_MAX, tb, blob_data(b.head), blob_len(b.head)); + + config.budget = 65; + if (tb[GLOBAL_ATTR_BUDGET]) + config.budget = blobmsg_get_u32(tb[GLOBAL_ATTR_BUDGET]); + + if (tb[GLOBAL_ATTR_GUARD]) + config.budget_guard = blobmsg_get_u32(tb[GLOBAL_ATTR_GUARD]); + else + config.budget_guard = config.budget / 10; +} + +static void +config_load(int init) +{ + struct uci_context *uci = uci_alloc_context(); + struct uci_package *package = NULL; + + memset(config.ports, 0, sizeof(config.ports)); + + if (!uci_load(uci, "poe", &package)) { + struct uci_element *e; + + if (init) + uci_foreach_element(&package->sections, e) { + struct uci_section *s = uci_to_section(e); + + if (!strcmp(s->type, "global")) + config_load_global(s); + } + uci_foreach_element(&package->sections, e) { + struct uci_section *s = uci_to_section(e); + + if (!strcmp(s->type, "port")) + config_load_port(s); + } + } + + uci_unload(uci, package); + uci_free_context(uci); +} + +static void +poe_cmd_dump(char *type, unsigned char *data) +{ + int i; + + if (!config.debug) + return; + + fprintf(stderr, "%s ->", type); + for (i = 0; i < 12; i++) + fprintf(stderr, " 0x%02x", data[i]); + fprintf(stderr, "\n"); +} + +static int +poe_cmd_send(struct cmd *cmd) +{ + poe_cmd_dump("TX", cmd->cmd); + ustream_write(&stream.stream, (char *)cmd->cmd, 12, false); + + return 0; +} + +static int +poe_cmd_next(void) +{ + struct cmd *cmd; + + if (list_empty(&cmd_pending)) + return -1; + + cmd = list_first_entry(&cmd_pending, struct cmd, list); + + return poe_cmd_send(cmd); +} + +static int +poe_cmd_queue(unsigned char *_cmd, int len) +{ + int i, empty = list_empty(&cmd_pending); + struct cmd *cmd = malloc(sizeof(*cmd)); + + memset(cmd, 0, sizeof(*cmd)); + memset(cmd->cmd, 0xff, 12); + memcpy(cmd->cmd, _cmd, len); + + cmd_seq++; + cmd->cmd[1] = cmd_seq; + cmd->cmd[11] = 0; + + for (i = 0; i < 11; i++) + cmd->cmd[11] += cmd->cmd[i]; + + list_add_tail(&cmd->list, &cmd_pending); + + if (empty) + return poe_cmd_send(cmd); + + return 0; +} + +/* 0x00 - Set port enable + * 0: Disable + * 1: Enable + */ +static int +poe_cmd_port_enable(unsigned char port, unsigned char enable) +{ + unsigned char cmd[] = { 0x00, 0x00, port, enable }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x06 - Set global port enable + * 0: Disable PSE Functionality on all Ports + * 1: Enable PSE Functionality on all Ports + * 2: Enable Force power Functionality on all ports + * 3: Enable Force Power with Disconnect Functionality on all Ports + */ +static int +poe_cmd_global_port_enable(unsigned char enable) +{ + unsigned char cmd[] = { 0x06, 0x00, enable }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x10 - Set port detection type + * 1: Legacy Capacitive Detection only + * 2: IEEE 802.3af 4-Point Detection only (Default) + * 3: IEEE 802.3af 4-Point followed by Legacy + * 4: IEEE 802.3af 2-Point detection (Not Supported) + * 5: IEEE 802.3af 2-Point followed by Legacy + */ +static int +poe_cmd_port_detection_type(unsigned char port, unsigned char type) +{ + unsigned char cmd[] = { 0x10, 0x00, port, type }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x11 - Set port classification + * 0: Disable + * 1: Enable + */ +static int +poe_cmd_port_classification(unsigned char port, unsigned char classification) +{ + unsigned char cmd[] = { 0x11, 0x00, port, classification }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x13 - Set port disconnect type + * 0: none + * 1: AC-disconnect + * 2: DC-disconnect + * 3: DC with delay + */ +static int +poe_cmd_port_disconnect_type(unsigned char port, unsigned char type) +{ + unsigned char cmd[] = { 0x13, 0x00, port, type }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x15 - Set port power limit type + * 0: None. Power limit is 16.2W if the connected device is “low power”, + * or the set high power limit if the device is “high power”. + * 1: Class based. The power limit for class 4 devices is determined by the high power limit. + * 2: User defined + */ +static int +poe_cmd_port_power_limit_type(unsigned char port, unsigned char type) +{ + unsigned char cmd[] = { 0x15, 0x00, port, type }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x16 - Set port power budget + * values in 0.2W increments + */ +static int +poe_cmd_port_power_budget(unsigned char port, unsigned char budget) +{ + unsigned char cmd[] = { 0x16, 0x00, port, budget }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x17 - Set power management mode + * 0: None (No Power Management mode) (Default in Semi-Auto mode) + * 1: Static Power Management with Port Priority(Default in Automode) + * 2: Dynamic Power Management with Port Priority + * 3: Static Power Management without Port Priority + * 4: Dynamic Power Management without Port Priority + */ +static int +poe_cmd_power_mgmt_mode(unsigned char mode) +{ + unsigned char cmd[] = { 0x18, 0x00, mode }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x18 - Set global power budget */ +static int +poe_cmd_global_power_budget(int budget, int guard) +{ + unsigned char cmd[] = { 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + cmd[3] = budget * 10 / 256; + cmd[4] = budget * 10 % 256; + cmd[5] = guard * 10 / 256; + cmd[6] = guard * 10 % 256; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x1a - Set port priority + * 0: Low + * 1: Normal + * 2: High + * 3: Critical + */ +static int +poe_set_port_priority(unsigned char port, unsigned char priority) +{ + unsigned char cmd[] = { 0x1a, 0x00, port, priority }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x1c - Set port power-up mode + * 0: PoE + * 1: legacy + * 2: pre-PoE+ + * 3: PoE+ + */ +static int +poe_set_port_power_up_mode(unsigned char port, unsigned char mode) +{ + unsigned char cmd[] = { 0x1c, 0x00, port, mode }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +/* 0x20 - Get system info */ +static int +poe_cmd_status(void) +{ + unsigned char cmd[] = { 0x20 }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +static int +poe_reply_status(unsigned char *reply) +{ + static char *mode[]={ + "Semi-auto I2C", + "Semi-auto UART", + "Auto I2C", + "Auto UART" + }; + static char *mcu[]={ + "ST Micro ST32F100 Microcontroller", + "Nuvoton M05xx LAN Microcontroller", + "ST Micro STF030C8 Microcontroller", + "Nuvoton M058SAN Microcontroller", + "Nuvoton NUC122 Microcontroller" + }; + static char *status[]={ + "Global Disable pin is de-asserted:No system reset from the previous query cmd:Configuration saved", + "Global Disable pin is de-asserted:No system reset from the previous query cmd:Configuration Dirty", + "Global Disable pin is de-asserted:System reseted:Configuration saved", + "Global Disable pin is de-asserted:System reseted:Configuration Dirty", + "Global Disable Pin is asserted:No system reset from the previous query cmd:Configuration saved", + "Global Disable Pin is asserted:No system reset from the previous query cmd:Configuration Dirty", + "Global Disable Pin is asserted:System reseted:Configuration saved", + "Global Disable Pin is asserted:System reseted:Configuration Dirty" + }; + + state.sys_mode = GET_STR(reply[2], mode); + config.port_count = reply[3]; + state.sys_version = reply[7]; + state.sys_mcu = GET_STR(reply[8], mcu); + state.sys_status = GET_STR(reply[9], status); + state.sys_ext_version = reply[10]; + + return 0; +} + +/* 0x23 - Get power statistics */ +static int +poe_cmd_power_stats(void) +{ + unsigned char cmd[] = { 0x23 }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +static int +poe_reply_power_stats(unsigned char *reply) +{ + state.power_consumption = reply[2]; + state.power_consumption *= 256; + state.power_consumption += reply[3]; + state.power_consumption /= 10; + + return 0; +} + +/* 0x26 - Get extended port config */ +static int +poe_cmd_port_ext_config(unsigned char port) +{ + unsigned char cmd[] = { 0x26, 0x00, port }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +static int +poe_reply_port_ext_config(unsigned char *reply) +{ + static char *mode[] = { + "PoE", + "Legacy", + "pre-PoE+", + "PoE+" + }; + + state.ports[reply[2]].poe_mode = GET_STR(reply[3], mode); + + return 0; +} + +/* 0x2a - Get all port status */ +static int +poe_cmd_port_overview(void) +{ + unsigned char cmd[] = { 0x2a, 0x00, 0x00 }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +static int +poe_reply_port_overview(unsigned char *reply) +{ + static char *status[]={ + "Disabled", + "Searching", + "Delivering power", + "Fault", + "Other fault", + "Requesting power", + }; + int i; + + for (i = 0; i < 8; i++) + state.ports[i].status = GET_STR((reply[3 + i] & 0xf), status); + + return 0; +} + +/* 0x30 - Get port power statistics */ +static int +poe_cmd_port_power_stats(unsigned char port) +{ + unsigned char cmd[] = { 0x30, 0x00, port }; + + return poe_cmd_queue(cmd, sizeof(cmd)); +} + +static int +poe_reply_port_power_stats(unsigned char *reply) +{ + float watt; + + watt = reply[9]; + watt *= 256; + watt += reply[10]; + watt /= 10; + + state.ports[reply[2]].watt = watt; + + return 0; +} + +static poe_reply_handler reply_handler[] = { + [0x20] = poe_reply_status, + [0x23] = poe_reply_power_stats, + [0x26] = poe_reply_port_ext_config, + [0x2a] = poe_reply_port_overview, + [0x30] = poe_reply_port_power_stats, +}; + +static int +poe_reply_consume(unsigned char *reply) +{ + struct cmd *cmd = NULL; + unsigned char sum = 0, i; + + poe_cmd_dump("RX", reply); + + if (list_empty(&cmd_pending)) { + ULOG_ERR("received unsolicited reply\n"); + return -1; + } + + cmd = list_first_entry(&cmd_pending, struct cmd, list); + list_del(&cmd->list); + + for (i = 0; i < 11; i++) + sum += reply[i]; + + if (reply[11] != sum) { + ULOG_DBG("received reply with bad checksum\n"); + return -1; + } + + if (reply[0] != cmd->cmd[0]) { + ULOG_DBG("received reply with bad command id\n"); + return -1; + } + + if (reply[1] != cmd->cmd[1]) { + ULOG_DBG("received reply with bad sequence number\n"); + return -1; + } + + free(cmd); + + if (reply_handler[reply[0]]) + return reply_handler[reply[0]](reply); + + return 0; +} + +static void +poe_stream_msg_cb(struct ustream *s, int bytes) +{ + int len; + unsigned char *reply = (unsigned char *)ustream_get_read_buf(s, &len); + + if (len < 12) + return; + + poe_reply_consume(reply); + ustream_consume(s, 12); + poe_cmd_next(); +} + +static void +poe_stream_notify_cb(struct ustream *s) +{ + if (!s->eof) + return; + + ULOG_ERR("tty error, shutting down\n"); + exit(-1); +} + +static int +poe_stream_open(char *dev, struct ustream_fd *s, speed_t speed) +{ + struct termios tio; + int tty; + + tty = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (tty < 0) { + ULOG_ERR("%s: device open failed: %s\n", dev, strerror(errno)); + return -1; + } + + tcgetattr(tty, &tio); + tio.c_cflag |= CREAD; + tio.c_cflag |= CS8; + tio.c_iflag |= IGNPAR; + tio.c_lflag &= ~(ICANON); + tio.c_lflag &= ~(ECHO); + tio.c_lflag &= ~(ECHOE); + tio.c_lflag &= ~(ISIG); + tio.c_iflag &= ~(IXON | IXOFF | IXANY); + tio.c_cflag &= ~CRTSCTS; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + cfsetispeed(&tio, speed); + cfsetospeed(&tio, speed); + tcsetattr(tty, TCSANOW, &tio); + + s->stream.string_data = false; + s->stream.notify_read = poe_stream_msg_cb; + s->stream.notify_state = poe_stream_notify_cb; + + ustream_fd_init(s, tty); + tcflush(tty, TCIFLUSH); + + return 0; +} + +static int +poe_port_setup(void) +{ + int i; + + for (i = 0; i < config.port_count; i++) { + if (!config.ports[i].enable) { + poe_cmd_port_enable(i, 0); + continue; + } + + poe_set_port_priority(i, config.ports[i].priority); + poe_set_port_power_up_mode(i, config.ports[i].power_up_mode); + if (config.ports[i].power_budget) { + poe_cmd_port_power_budget(i, config.ports[i].power_budget); + poe_cmd_port_power_limit_type(i, 2); + } else { + poe_cmd_port_power_limit_type(i, 1); + } + poe_cmd_port_disconnect_type(i, 2); + poe_cmd_port_classification(i, 1); + poe_cmd_port_detection_type(i, 3); + poe_cmd_port_enable(i, 1); + } + + return 0; +} + +static int +poe_initial_setup(void) +{ + poe_cmd_status(); + poe_cmd_power_mgmt_mode(2); + poe_cmd_global_power_budget(0, 0); + poe_cmd_global_port_enable(0); + poe_cmd_global_power_budget(config.budget, config.budget_guard); + + poe_port_setup(); + + return 0; +} + +static void +state_timeout_cb(struct uloop_timeout *t) +{ + int i; + + poe_cmd_power_stats(); + poe_cmd_port_overview(); + + for (i = 0; i < config.port_count; i++) { + poe_cmd_port_ext_config(i); + poe_cmd_port_power_stats(i); + } + + uloop_timeout_set(&state_timeout, 2 * 1000); +} + +static int +ubus_poe_info_cb(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + char tmp[16]; + void *c; + int i; + + blob_buf_init(&b, 0); + + snprintf(tmp, sizeof(tmp), "v%u.%u", + state.sys_version, state.sys_ext_version); + blobmsg_add_string(&b, "firmware", tmp); + if (state.sys_mcu) + blobmsg_add_string(&b, "mcu", state.sys_mcu); + blobmsg_add_double(&b, "budget", config.budget); + blobmsg_add_double(&b, "consumption", state.power_consumption); + + c = blobmsg_open_table(&b, "ports"); + for (i = 0; i < config.port_count; i++) { + void *p; + + if (!config.ports[i].enable) + continue; + + p = blobmsg_open_table(&b, config.ports[i].name); + + blobmsg_add_u32(&b, "priority", config.ports[i].priority); + + if (state.ports[i].poe_mode) + blobmsg_add_string(&b, "mode", state.ports[i].poe_mode); + if (state.ports[i].status) + blobmsg_add_string(&b, "status", state.ports[i].status); + else + blobmsg_add_string(&b, "status", "unknown"); + if (state.ports[i].watt) + blobmsg_add_double(&b, "consumption", state.ports[i].watt); + + blobmsg_close_table(&b, p); + } + blobmsg_close_table(&b, c); + + ubus_send_reply(ctx, req, b.head); + + return UBUS_STATUS_OK; +} + +static int +ubus_poe_reload_cb(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + config_load(0); + poe_port_setup(); + + return UBUS_STATUS_OK; +} + +static const struct ubus_method ubus_poe_methods[] = { + UBUS_METHOD_NOARG("info", ubus_poe_info_cb), + UBUS_METHOD_NOARG("reload", ubus_poe_reload_cb), +}; + +static struct ubus_object_type ubus_poe_object_type = + UBUS_OBJECT_TYPE("poe", ubus_poe_methods); + +static struct ubus_object ubus_poe_object = { + .name = "poe", + .type = &ubus_poe_object_type, + .methods = ubus_poe_methods, + .n_methods = ARRAY_SIZE(ubus_poe_methods), +}; + +static void +ubus_connect_handler(struct ubus_context *ctx) +{ + int ret; + + ret = ubus_add_object(ctx, &ubus_poe_object); + if (ret) + fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret)); +} + +int +main(int argc, char ** argv) +{ + int ch; + + ulog_open(ULOG_STDIO | ULOG_SYSLOG, LOG_DAEMON, "realtek-poe"); + ulog_threshold(LOG_INFO); + + while ((ch = getopt(argc, argv, "d")) != -1) { + switch (ch) { + case 'd': + config.debug = 1; + break; + } + } + + config_load(1); + + uloop_init(); + conn.cb = ubus_connect_handler; + ubus_auto_connect(&conn); + + if (poe_stream_open("/dev/ttyS1", &stream, B19200) < 0) + return -1; + + poe_initial_setup(); + state_timeout.cb = state_timeout_cb; + uloop_timeout_set(&state_timeout, 1000); + uloop_run(); + uloop_done(); + + return 0; +}