From 5c514051a2548fe3510f223971e6bd8ab8f1bf13 Mon Sep 17 00:00:00 2001 From: Xiaofeng Meng Date: Wed, 3 Dec 2025 09:57:18 +0100 Subject: [PATCH] Add dm-framework for bridgemngr Introduce dm-framework.mk providing reusable build macros for packages that integrate with the DM Framework: - Build/Compile/DM: Generates C code and shared libraries from JSON data models using json2code.js, compiles them, and creates package-specific .so files - Build/Install/DM: Installs generated libraries, JS handlers, and data model files to the dm-framework directory(dmf) Also adds bridgemngr as the first package utilizing these helpers to implement bridge data model with dm-framework, which can be enabled through flag CONFIG_BRIDGEMNGR_USE_DM_FRAMEWORK. The commit also include changes in bbfdm dm-framework adaption. --- bbfdm/Makefile | 6 +- bbfdm/files/etc/init.d/bbfdm.services | 21 +- bridgemngr/Config.in | 5 + bridgemngr/Makefile | 40 ++- bridgemngr/dm/VLANBridge.json | 449 ++++++++++++++++++++++++++ bridgemngr/dm/bridge-apply.js | 343 ++++++++++++++++++++ bridgemngr/dm/bridge-import.js | 268 +++++++++++++++ bridgemngr/dm/bridge.js | 133 ++++++++ bridgemngr/dm/common.js | 56 ++++ dm-framework/Makefile | 170 ++++++++++ dm-framework/dm-framework.mk | 33 ++ dm-framework/dmf_service.json | 17 + quickjs/Makefile | 4 + 13 files changed, 1529 insertions(+), 16 deletions(-) create mode 100644 bridgemngr/dm/VLANBridge.json create mode 100644 bridgemngr/dm/bridge-apply.js create mode 100644 bridgemngr/dm/bridge-import.js create mode 100644 bridgemngr/dm/bridge.js create mode 100755 bridgemngr/dm/common.js create mode 100755 dm-framework/Makefile create mode 100644 dm-framework/dm-framework.mk create mode 100644 dm-framework/dmf_service.json diff --git a/bbfdm/Makefile b/bbfdm/Makefile index a7c93c39b..aa635f00c 100644 --- a/bbfdm/Makefile +++ b/bbfdm/Makefile @@ -11,7 +11,7 @@ USE_LOCAL:=0 ifneq ($(USE_LOCAL),1) PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://dev.iopsys.eu/bbf/bbfdm.git -PKG_SOURCE_VERSION:=72c3307651cb583121fa5b4abcaad957ddc264bd +PKG_SOURCE_VERSION:=34378ab7c9e844e6638fdb2ea557d2fd21114bb0 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz PKG_MIRROR_HASH:=skip endif @@ -149,10 +149,10 @@ define Package/bbfdmd/install $(INSTALL_DIR) $(1)/etc/hotplug.d/iface $(INSTALL_BIN) ./files/etc/hotplug.d/iface/85-bbfdm-sysctl $(1)/etc/hotplug.d/iface/85-bbfdm-sysctl - + $(INSTALL_DIR) $(1)/lib/upgrade/keep.d $(INSTALL_DATA) ./files/lib/upgrade/keep.d/bbf $(1)/lib/upgrade/keep.d/bbf - + $(INSTALL_DIR) $(1)/etc/uci-defaults $(INSTALL_BIN) ./files/etc/uci-defaults/91-fix-bbfdmd-enabled-option $(1)/etc/uci-defaults/ $(INSTALL_BIN) ./files/etc/uci-defaults/ruleng.bbfdm $(1)/etc/uci-defaults diff --git a/bbfdm/files/etc/init.d/bbfdm.services b/bbfdm/files/etc/init.d/bbfdm.services index c376e7a76..bf3a00608 100644 --- a/bbfdm/files/etc/init.d/bbfdm.services +++ b/bbfdm/files/etc/init.d/bbfdm.services @@ -5,6 +5,7 @@ STOP=05 USE_PROCD=1 PROG=/usr/sbin/dm-service +DM_AGENT_PROG=/usr/sbin/dm-agent BBFDM_MICROSERVICE_DIR="/etc/bbfdm/services" @@ -24,7 +25,8 @@ validate_bbfdm_micro_service_section() _add_microservice() { local name path loglevel - local enable enable_core unified_daemon + local enable enable_core unified_daemon dm_framework + local daemon_prog # Check enable from micro-service path="${1}" @@ -47,14 +49,25 @@ _add_microservice() return 0 fi + json_get_var dm_framework dm-framework 0 + if [ "${dm_framework}" -eq "1" ] || [ "${dm_framework}" = "true" ]; then + daemon_prog="${DM_AGENT_PROG}" + else + daemon_prog="${PROG}" + fi + json_select config json_get_var loglevel loglevel 4 procd_open_instance "${name}" - procd_set_param command ${PROG} - procd_append_param command -m "${name}" - procd_append_param command -l "${loglevel}" + procd_set_param command ${daemon_prog} + + # Only add parameters for dm-service, not for dm-agent + if [ "${daemon_prog}" = "${PROG}" ]; then + procd_append_param command -m "${name}" + procd_append_param command -l "${loglevel}" + fi if [ "${enable_core}" -eq "1" ]; then procd_set_param limits core="unlimited" diff --git a/bridgemngr/Config.in b/bridgemngr/Config.in index 4c7bec466..bf257b276 100644 --- a/bridgemngr/Config.in +++ b/bridgemngr/Config.in @@ -20,4 +20,9 @@ config BRIDGEMNGR_BRIDGE_VENDOR_EXT config BRIDGEMNGR_BRIDGE_VENDOR_PREFIX string "Package specific datamodel Vendor Prefix for TR181 extensions" default "" + +config BRIDGEMNGR_USE_DM_FRAMEWORK + bool "Use new DM framework support" + default n + endif diff --git a/bridgemngr/Makefile b/bridgemngr/Makefile index 5d03296c2..0c2a17d89 100644 --- a/bridgemngr/Makefile +++ b/bridgemngr/Makefile @@ -20,27 +20,39 @@ PKG_LICENSE:=GPL-2.0-only PKG_LICENSE_FILES:=LICENSE include $(INCLUDE_DIR)/package.mk + +ifneq ($(CONFIG_BRIDGEMNGR_USE_DM_FRAMEWORK),y) include ../bbfdm/bbfdm.mk +else +include ../dm-framework/dm-framework.mk +endif define Package/bridgemngr - CATEGORY:=Utilities - TITLE:=Bridge Manager - DEPENDS:=+libuci +libubox +libubus +libblobmsg-json - DEPENDS+=+libbbfdm-api +libbbfdm-ubus +dm-service + CATEGORY:=Utilities + TITLE:=Bridge Manager + DEPENDS:=+libuci +libubox +libubus +libblobmsg-json +ifeq ($(CONFIG_BRIDGEMNGR_USE_DM_FRAMEWORK),y) + DEPENDS+= +dm-framework + PKG_BUILD_DEPENDS:=dm-framework +else + DEPENDS+= +libbbfdm-api +libbbfdm-ubus +dm-service +endif endef define Package/bridgemngr/description - Package to add Device.Bridging. data model support. + Package to add Device.Bridging. data model support. endef define Package/$(PKG_NAME)/config - source "$(SOURCE)/Config.in" + source "$(SOURCE)/Config.in" endef +ifneq ($(CONFIG_BRIDGEMNGR_USE_DM_FRAMEWORK),y) MAKE_PATH:=src +endif ifeq ($(CONFIG_BRIDGEMNGR_BRIDGE_VENDOR_PREFIX),"") -VENDOR_PREFIX = $(CONFIG_BBF_VENDOR_PREFIX) +VENDOR_PREFIX = $(if $(CONFIG_BBF_VENDOR_PREFIX),$(CONFIG_BBF_VENDOR_PREFIX),$(CONFIG_DM_FRAMEWORK_VENDOR_PREFIX)) else VENDOR_PREFIX = $(CONFIG_BRIDGEMNGR_BRIDGE_VENDOR_PREFIX) endif @@ -48,22 +60,32 @@ endif TARGET_CFLAGS += -DBBF_VENDOR_PREFIX=\\\"$(VENDOR_PREFIX)\\\" ifeq ($(CONFIG_BRIDGEMNGR_BRIDGE_VLAN),y) - TARGET_CFLAGS += -DBRIDGE_VLAN_BACKEND +TARGET_CFLAGS += -DBRIDGE_VLAN_BACKEND endif ifeq ($(CONFIG_BRIDGEMNGR_COPY_PBITS),y) - TARGET_CFLAGS+=-DBRIDGEMNGR_COPY_PBITS +TARGET_CFLAGS += -DBRIDGEMNGR_COPY_PBITS +endif + +ifeq ($(CONFIG_BRIDGEMNGR_USE_DM_FRAMEWORK),y) +define Build/Compile + $(call Build/Compile/DM,./dm,$(PKG_BUILD_DIR)/dm_build,$(VENDOR_PREFIX)) +endef endif define Package/bridgemngr/install $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_DIR) $(1)/etc/config +ifeq ($(CONFIG_BRIDGEMNGR_USE_DM_FRAMEWORK),y) + $(call Build/Install/DM,./dm,$(PKG_BUILD_DIR)/dm_build,$(1),bridgemngr) +else $(BBFDM_REGISTER_SERVICES) ./bbfdm_service.json $(1) $(PKG_NAME) $(BBFDM_INSTALL_MS_DM) $(PKG_BUILD_DIR)/src/libbridgemngr.so $(1) $(PKG_NAME) ifeq ($(CONFIG_BRIDGEMNGR_BRIDGE_VENDOR_EXT), y) $(BBFDM_INSTALL_MS_PLUGIN) $(PKG_BUILD_DIR)/src/libbridgeext.so $(1) $(PKG_NAME) 10 $(BBFDM_INSTALL_MS_PLUGIN) -v ${VENDOR_PREFIX} ./files/VLAN_Filtering_Extension.json $(1) $(PKG_NAME) 11 +endif endif $(INSTALL_BIN) ./files/etc/init.d/bridging $(1)/etc/init.d/ diff --git a/bridgemngr/dm/VLANBridge.json b/bridgemngr/dm/VLANBridge.json new file mode 100644 index 000000000..c0b39a8b2 --- /dev/null +++ b/bridgemngr/dm/VLANBridge.json @@ -0,0 +1,449 @@ +[ + { + "object": "Device.Bridging.", + "access": "readOnly", + "parameters": [ + { + "name": "MaxBridgeEntries", + "access": "readOnly", + "dataType": "unsignedInt", + "const" : "4094" + }, + { + "name": "MaxDBridgeEntries", + "access": "readOnly", + "dataType": "unsignedInt", + "const" : "4094" + }, + { + "name": "MaxQBridgeEntries", + "access": "readOnly", + "dataType": "unsignedInt", + "const" : "4094" + }, + { + "name": "MaxVLANEntries", + "access": "readOnly", + "dataType": "unsignedInt", + "const" : "4094" + }, + { + "name": "BridgeNumberOfEntries", + "access": "readOnly", + "dataType": "unsignedInt" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.", + "uniqueKeys": "Name,Alias", + "access": "readWrite", + "uci": "network.device", + "flags": [ + "dmmap" + ], + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean", + "uci": "enabled", + "uci-default": "true" + }, + { + "name": "Name", + "access": "readOnly", + "dataType": "string(:64)", + "set_on_create": "bridge_", + "db": true + }, + { + "name": "Alias", + "access": "readWrite", + "dataType": "string(:64)" + }, + { + "name": "Status", + "access": "readOnly", + "dataType": "enum", + "enum": [ + "Disabled", + "Enabled", + "Error" + ], + "default": "Disabled" + }, + { + "name": "Standard", + "access": "readWrite", + "dataType": "enum", + "enum": [ + "802.1D-2004", + "802.1Q-2005", + "802.1Q-2011" + ], + "default": "802.1Q-2011" + }, + { + "name": "PortNumberOfEntries", + "access": "readOnly", + "dataType": "unsignedInt" + }, + { + "name": "VLANNumberOfEntries", + "access": "readOnly", + "dataType": "unsignedInt" + }, + { + "name": "VLANPortNumberOfEntries", + "access": "readOnly", + "dataType": "unsignedInt" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.STP.", + "access": "readOnly", + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean", + "uci": "stp" + }, + { + "name": "Status", + "access": "readOnly", + "dataType": "enum", + "enum": [ + "Disabled", + "Enabled", + "Error_Misconfigured", + "Error" + ], + "default": "Disabled" + }, + { + "name": "Protocol", + "access": "readWrite", + "dataType": "enum", + "enum": [ + "STP", + "RSTP" + ] + }, + { + "name": "BridgePriority", + "access": "readWrite", + "dataType": "unsignedInt(0:61440)", + "default": "32768" + }, + { + "name": "HelloTime", + "access": "readWrite", + "dataType": "unsignedInt(100:1000)", + "default": "200" + }, + { + "name": "MaxAge", + "access": "readWrite", + "dataType": "unsignedInt(600:4000)", + "default": "2000" + }, + { + "name": "ForwardingDelay", + "access": "readWrite", + "dataType": "unsignedInt(4:30)", + "default": "15" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.Port.{i}.", + "uniqueKeys": "Alias,Name", + "access": "readWrite", + "flags": [ + "dmmap" + ], + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "Status", + "access": "readOnly", + "dataType": "enum", + "enum": [ + "Up", + "Down", + "Unknown", + "Dormant", + "NotPresent", + "LowerLayerDown", + "Error" + ], + "default": "Down" + }, + { + "name": "Alias", + "access": "readWrite", + "dataType": "string(:64)" + }, + { + "name": "Name", + "access": "readOnly", + "dataType": "string(:64)", + "set_on_create": "port_", + "db": "true", + "flags": [ + "linker" + ], + "js-value": "ifname" + }, + { + "name": "LastChange", + "access": "readOnly", + "dataType": "unsignedInt", + "const": "0" + }, + { + "name": "LowerLayers", + "access": "readWrite", + "dataType": "pathRef[]", + "pathRef": [ + "Device.Bridging.Bridge.{i}.Port." + ], + "js-value": "ssidPath" + }, + { + "name": "ManagementPort", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "PriorityRegeneration", + "access": "readWrite", + "dataType": "unsignedInt(0:7)[]", + "default": "0,1,2,3,4,5,6,7" + }, + { + "name": "{BBF_VENDOR_PREFIX}EgressPriorityRegeneration", + "access": "readWrite", + "dataType": "unsignedInt(0:7)[]" + }, + { + "name": "Type", + "access": "readWrite", + "dataType": "enum", + "enum": [ + "ProviderNetworkPort", + "CustomerNetworkPort", + "CustomerEdgePort", + "CustomerVLANPort", + "VLANUnawarePort" + ], + "default": "CustomerVLANPort" + }, + { + "name": "PVID", + "access": "readWrite", + "dataType": "int(1:4094)", + "default": "1" + }, + { + "name": "TPID", + "access": "readWrite", + "dataType": "unsignedInt", + "default": "33024" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.Port.{i}.Stats.", + "access": "readOnly", + "parameters": [ + { + "name": "BytesSent", + "dataType": "unsignedLong" + }, + { + "name": "BytesReceived", + "dataType": "unsignedLong" + }, + { + "name": "PacketsSent", + "dataType": "unsignedLong" + }, + { + "name": "PacketsReceived", + "dataType": "unsignedLong" + }, + { + "name": "ErrorsSent", + "dataType": "StatsCounter32" + }, + { + "name": "ErrorsReceived", + "dataType": "StatsCounter32" + }, + { + "name": "UnicastPacketsSent", + "dataType": "unsignedLong" + }, + { + "name": "DiscardPacketsSent", + "dataType": "StatsCounter32" + }, + { + "name": "DiscardPacketsReceived", + "dataType": "StatsCounter32" + }, + { + "name": "MulticastPacketsSent", + "dataType": "unsignedLong" + }, + { + "name": "UnicastPacketsReceived", + "dataType": "unsignedLong" + }, + { + "name": "MulticastPacketsReceived", + "dataType": "unsignedLong" + }, + { + "name": "BroadcastPacketsSent", + "dataType": "unsignedLong" + }, + { + "name": "BroadcastPacketsReceived", + "dataType": "unsignedLong" + }, + { + "name": "UnknownProtoPacketsReceived", + "dataType": "StatsCounter32" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.VLAN.{i}.", + "uniqueKeys": "Alias,VLANID", + "access": "readWrite", + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "Name", + "access": "readWrite", + "dataType": "string(:64)", + "set_on_create": "vlan_" + }, + { + "name": "Alias", + "access": "readWrite", + "dataType": "string(:64)" + }, + { + "name": "VLANID", + "access": "readWrite", + "dataType": "int(0:4094)" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.VLANPort.{i}.", + "uniqueKeys": "Alias,VLAN", + "access": "readWrite", + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "Alias", + "access": "readWrite", + "dataType": "string(:64)" + }, + { + "name": "VLAN", + "access": "readWrite", + "dataType": "pathRef", + "pathRef": [ + "Device.Bridging.Bridge.{i}.VLAN." + ] + }, + { + "name": "Port", + "access": "readWrite", + "dataType": "pathRef", + "pathRef": [ + "Device.Bridging.Bridge.{i}.Port." + ] + }, + { + "name": "Untagged", + "access": "readWrite", + "dataType": "boolean" + } + ] + }, + { + "object": "Device.Bridging.ProviderBridge.{i}.", + "uniqueKeys": "Alias", + "access": "readWrite", + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "Status", + "access": "readOnly", + "dataType": "enum", + "enum": [ + "Disabled", + "Enabled", + "Error_Misconfigured", + "Error" + ], + "default": "Disabled" + }, + { + "name": "Alias", + "access": "readWrite", + "dataType": "string(:64)" + }, + { + "name": "Type", + "access": "readWrite", + "dataType": "enum", + "enum": [ + "S-VLAN", + "PE" + ] + }, + { + "name": "SVLANcomponent", + "access": "readWrite", + "dataType": "pathRef", + "pathRef": [ + "Device.Bridging.Bridge." + ] + }, + { + "name": "CVLANcomponents", + "access": "readWrite", + "dataType": "pathRef[]", + "pathRef": [ + "Device.Bridging.Bridge." + ] + } + ] + } +] \ No newline at end of file diff --git a/bridgemngr/dm/bridge-apply.js b/bridgemngr/dm/bridge-apply.js new file mode 100644 index 000000000..8b38d9cfb --- /dev/null +++ b/bridgemngr/dm/bridge-apply.js @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2025 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +import { + getUciOption, getUciByType, setUci, addUci, delUci +} from '../uci.js'; +import { getParamValue, replaceArrayElement, isTrue } from '../utils.js'; +import * as dm from './dm_consts.js'; +import { getBridgeDeviceType, getTPIDFromDeviceType } from './common.js'; + +function findVLANPort(vlanPort, VLANs, Ports) { + const [, vlanIndices] = _dm_node(vlanPort.VLAN); + const vlanIdx = vlanIndices[vlanIndices.length - 1]; + const vlan = VLANs?.find(x => x['.index'] === vlanIdx); + if (!vlan) { + _log_error(`vlan not found for vlanPort: ${vlanPort.VLAN}`); + return; + } + + const [, portIndices] = _dm_node(vlanPort.Port); + const portIdx = portIndices[portIndices.length - 1]; + const port = Ports?.find(x => x['.index'] === portIdx); + if (!port) { + _log_error(`port not found for vlanPort: ${vlanPort.Port}`); + return; + } + + return [vlan, port]; +} + +function createVLANDevice(devName, ifname, VLAN, Port) { + const ingress_qos_mapping = Port.PriorityRegeneration !== '0,1,2,3,4,5,6,7' + ? Port.PriorityRegeneration.split(',').map((p, i) => `${i}:${p}`) + : ''; + + const egress_qos_mapping = Port.X_IOPSYS_EU_EgressPriorityRegeneration !== '' + ? Port.X_IOPSYS_EU_EgressPriorityRegeneration.split(',').map((p, i) => `${i}:${p}`) + : ''; + + const uciConfigs = { + ifname: ifname, + vid: VLAN.VLANID, + name: ifname + '.' + VLAN.VLANID, + type: getBridgeDeviceType(Port.Type), + tpid: getTPIDFromDeviceType(Port.Type, Port.TPID), + ingress_qos_mapping, + egress_qos_mapping, + }; + addUci('network', 'device', devName, uciConfigs); +} + +function applyBridge(bri, Ports, VLANs, VLANPorts) { + deinitDeviceBridgingBridge(bri._key, false); + + const ports = []; + + for (const vlan of VLANs || []) { + vlan.ports = []; + vlan.hasUntagged = false; + } + + for (const vlanPort of VLANPorts || []) { + if (!vlanPort.Enable || !vlanPort.Port || !vlanPort.VLAN) { + continue; + } + + const [vlan, port] = findVLANPort(vlanPort, VLANs, Ports); + if (!vlan || !port || port.LowerLayers === '' || !port.Enable || !vlan.Enable || vlan.VLANID <= 0) { + continue; + } + + port.used = true; + + if (port.Type === 'ProviderNetworkPort') { + continue; + } + + const devName = `br_${bri['.index']}_dev_${vlanPort['.index']}`; + if (!port.LowerLayers.startsWith('Device.Ethernet.Interface')) { + _log_error(`applyBridge, LowerLayers not found for port: ${port.LowerLayers}`); + continue; + } + + const ifname = _dm_linker_value(port.LowerLayers); + if (!ifname) { + _log_error(`applyBridge, ifname not found for port: ${port.LowerLayers}`); + continue; + } + + if (vlanPort.Untagged) { + ports.push(ifname); + vlan.hasUntagged = true; + vlan.ports.push(ifname + ':u' + (vlanPort.PVID === vlan.VLANID ? '*' : '')); + // vlan.ports.push(ifname + ':u*'); + } else { + createVLANDevice(devName, ifname, vlan, port); + const vlanDevName = ifname + '.' + vlan.VLANID; + ports.push(vlanDevName); + vlan.ports.push(vlanDevName + ':u*'); + } + } + + for (const port of Ports || []) { + if (port.used || isTrue(port.ManagementPort)) { + continue; + } + if (port.LowerLayers.startsWith('Device.Ethernet.Interface')) { + const ifname = _dm_linker_value(port.LowerLayers); + if (!ifname) { + _log_error(`applyBridge, ifname not found for port: ${port.LowerLayers}`); + continue; + } + ports.push(ifname); + } + } + + if (ports.length > 0) { + setUci('network', bri._key, { ports: ports }); + } + + // create the bridge-vlan for the untagged port + for (const vlan of VLANs || []) { + if (vlan.hasUntagged) { + addUci('network', 'bridge-vlan', `br_${bri['.index']}_bv_${vlan['.index']}`, { + device: bri.Name, + vlan: vlan.VLANID, + ports: vlan.ports, + }); + } + } + + applyProviderBridges(); +} + +function applyPEBridges(ifname, vlanID, portLowerLayers, cvlanBridgePath) {const vlanPorts = _dm_get(cvlanBridgePath + '.VLANPort.'); + for (const vlanPort of vlanPorts || []) { + if (!vlanPort.Enable || !vlanPort.Port || !vlanPort.VLAN) { + continue; + } + const portVals = _dm_get(vlanPort.Port); + if (portVals?.LowerLayers !== portLowerLayers || portVals?.Type !== 'CustomerEdgePort') { + _log_error(`applyPEBridges, portVals not found for vlanPort: ${vlanPort.Port}`); + continue; + } + + const vlan = _dm_get(vlanPort.VLAN); + if (!vlan || vlan.VLANID <= 0 || !vlan.Enable) { + _log_error(`applyPEBridges, vlan not found for vlanPort: ${vlanPort.VLAN}`); + continue; + } + + const devName = ifname + '.' + vlan.VLANID; + + const briName = getParamValue(cvlanBridgePath, '_key'); + if (!briName) { + _log_error(`applyPEBridges, briName not found for cvlanBridgePath: ${cvlanBridgePath}`); + continue; + } + + const ports = getUciOption('network', briName, 'ports') || []; + const devs = getUciByType('network', 'device', { match: { name: devName } }); + if (devs.length === 0) { + _log_error(`applyPEBridges, device not found for devName: ${devName}`); + continue; + } + + const newName = `${ifname}.${vlanID}.${vlan.VLANID}`; + setUci('network', devs[0]['.name'], {ifname: `${ifname}.${vlanID}`, name: newName}); + replaceArrayElement(ports, devName, newName); + setUci('network', briName, { ports: ports }); + } +} + +function applyProviderBridge(pbridgeIndex, type, svlanBridgePath, cvlanBridgePaths) { + const vlanPorts = _dm_get(svlanBridgePath + '.VLANPort.'); + const briName = getParamValue(svlanBridgePath, '_key'); + if (briName) { + delUci('network', briName); + } + for (const vlanPort of vlanPorts || []) { + if (!vlanPort.Enable || !vlanPort.Port || !vlanPort.VLAN) { + continue; + } + + const portVals = _dm_get(vlanPort.Port); + if (!portVals) { + _log_error(`applyProviderBridge, portVals not found for vlanPort: ${vlanPort.Port}`); + continue; + } + + if (portVals.Type !== 'ProviderNetworkPort') { + _log_error(`applyProviderBridge, portVals.Type is not ProviderNetworkPort for vlanPort: ${vlanPort.Port}`); + continue; + } + + const ifname = _dm_linker_value(portVals.LowerLayers); + if (!ifname) { + _log_error(`applyProviderBridge, ifname not found for port: ${portVals.LowerLayers}`); + continue; + } + + const vlan = _dm_get(vlanPort.VLAN); + if (!vlan || !vlan.Enable || vlan.VLANID <= 0) { + _log_error(`applyProviderBridge, vlan invalid for vlanPort: ${vlanPort.VLAN}`); + continue; + } + + const devName = `pb_${pbridgeIndex}_dev_${vlanPort['.index']}`; + createVLANDevice(devName, ifname, vlan, portVals); + + cvlanBridgePaths.split(',').forEach(cvlanBridgePath => { + if (type === 'S-VLAN') { + const briName = getParamValue(cvlanBridgePath, '_key'); + if (briName) { + let ports = getUciOption('network', briName, 'ports') || []; + ports = ports.filter(x => !x.startsWith(ifname)); + ports.push(ifname + '.' + vlan.VLANID); + setUci('network', briName, { ports: ports }); + } else { + _log_error(`applyProviderBridge, briName not found for cvlanBridgePath: ${cvlanBridgePath}`); + } + } else { + applyPEBridges(ifname, vlan.VLANID, portVals.LowerLayers, cvlanBridgePath); + } + }); + + } +} + +function applyProviderBridges() { + const pbridges = _dm_get(dm.DM_DEVICE_BRIDGING_PROVIDERBRIDGE); + for (const pbridge of pbridges || []) { + if (!pbridge.Enable || !pbridge.SVLANcomponent || !pbridge.CVLANcomponents || !pbridge.Type) { + continue; + } + + applyProviderBridge(pbridge['.index'], pbridge.Type, pbridge.SVLANcomponent, pbridge.CVLANcomponents); + } +} + +function applyAllBridges() { + const bridges = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE); + for (const bri of bridges || []) { + applyBridge(bri, bri.Port, bri.VLAN, bri.VLANPort); + } +} + +export function applyDeviceBridgingProviderBridge() { + applyAllBridges(); +} + +function isProviderBridge(ports) { + return ports.some(port => port.Type === 'ProviderNetworkPort' || port.Type === 'CustomerEdgePort'); +} + +export function applyDeviceBridgingBridgePort(ports, bri) { + const vlans = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLAN, bri['.index']); + const vlanPorts = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLANPORT, bri['.index']); + if (isProviderBridge(ports)) { + applyAllBridges(); + return; + } + applyBridge(bri, ports, vlans, vlanPorts); +} + +export function applyDeviceBridgingBridgeVLAN(vlans, bri) { + const ports = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_PORT, bri['.index']); + if (isProviderBridge(ports)) { + applyAllBridges(); + return; + } + const vlanPorts = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLANPORT, bri['.index']); + applyBridge(bri, ports, vlans, vlanPorts); +} + +export function applyDeviceBridgingBridgeVLANPort(vlanPorts, bri) { + const ports = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_PORT, bri['.index']); + if (isProviderBridge(ports)) { + applyAllBridges(); + return; + } + const vlans = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLAN, bri['.index']); + applyBridge(bri, ports, vlans, vlanPorts); +} + +export function initDeviceBridgingBridge(bri) { + setUci('network', bri._key, { + type: 'bridge', + name: bri.Name, + enabled: '0', + }); + // create empty interface for the bridge + addUci('network', 'interface', `itf_${bri._key}`, { + device: bri.Name, + bridge_empty: '1', + }); +} + +export const filterDeviceBridgingBridge = uci => uci.type === 'bridge'; + +function delVLANDevice(devName, devices, ethPorts) { + if (ethPorts.find(x => x.name === devName)) { + return; + } + + const dev = devices.find(x => x.name === devName); + if (!dev) { + return; + } + + // delete possible vlan stack device + delVLANDevice(dev.ifname, devices, ethPorts); + delUci('network', dev['.name']); +} + +export function deinitDeviceBridgingBridge(uci, removeInterface = true) { + const ports = getUciOption('network', uci, 'ports'); + const devices = getUciByType('network', 'device'); + + const ethPorts = devices.filter(x => x.type === undefined && x.eee !== undefined); + ports?.forEach(port => { + delVLANDevice(port, devices, ethPorts); + }); + + const name = getUciOption('network', uci, 'name'); + // delete related bridge-vlan devices (query first to avoid noisy "section missing" logs) + const bridgeVlans = getUciByType('network', 'bridge-vlan', { match: { device: name } }) || []; + bridgeVlans.forEach(vlan => delUci('network', vlan['.name'])); + + if (removeInterface) { + // delete the empty interface created for this bridge (query first) + const interfaces = getUciByType('network', 'interface', { match: { device: name, bridge_empty: '1' } }) || []; + interfaces.forEach(intf => delUci('network', intf['.name'])); + } +} diff --git a/bridgemngr/dm/bridge-import.js b/bridgemngr/dm/bridge-import.js new file mode 100644 index 000000000..c1a55245f --- /dev/null +++ b/bridgemngr/dm/bridge-import.js @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2025 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +import { getUciByType } from '../uci.js'; +import { getBridgePortType, getTPIDFromDeviceType } from './common.js'; + +// find the port from bridge-vlan; returns [vlanId, isTagged, isPvid] or null +function findPortFromBridgeVlan(bridgeVlans, portName) { + if (!bridgeVlans) return null; + + for (const bridgeVlan of bridgeVlans) { + const port = bridgeVlan.ports?.find(x => x.split(':')[0] === portName); + if (port) { + const flags = port.includes(':') ? port.split(':')[1] : ''; + return [bridgeVlan.vlan, flags.includes('t'), flags.includes('*')]; + } + } + return null; +} + +function isVLANSubInterface(portName, ethPorts) { + const names = portName.split('.'); + if (names.length > 1) { + const baseIfname = names.slice(0, -1).join('.'); + if (ethPorts.find(x => x.ifname === baseIfname)) { + return true; + } + } + return false; +} + +function createProviderBridge(dev, type, ethIndex, bridges, providerBridges, cVLANBridgeIndex) { + const briIndex = bridges.length + 1; + + let sVLANBridgeIndex = bridges.findIndex((x) => x['VLAN.']?.[0]?.VLANID === dev.vid && x['Port.']?.[0]?.LowerLayers === `Device.Ethernet.Interface.${ethIndex}`); + if (sVLANBridgeIndex < 0) { + // no management port needed for provider bridge + bridges.push({ + Name: dev.name, + Alias: `cpe-${dev.name}`, + Standard: '802.1Q-2011', + Enable: 1, + 'Port.': [{ + Enable: 1, + Name: dev.name, + Alias: `cpe-${dev.name}`, + TPID: 34984, + PVID: 1, + Type: 'ProviderNetworkPort', + LowerLayers: `Device.Ethernet.Interface.${ethIndex}`, + }], + 'VLAN.': [{ + Enable: 1, + VLANID: dev.vid, + }], + 'VLANPort.': [{ + Enable: 1, + VLAN: `Device.Bridging.Bridge.${briIndex}.VLAN.1`, + Port: `Device.Bridging.Bridge.${briIndex}.Port.1`, + Untagged: 1, + }], + _key: dev['.name'], + }); + sVLANBridgeIndex = bridges.length; + } else { + sVLANBridgeIndex = sVLANBridgeIndex + 1; + const pvBridge = providerBridges.find(x => x.SVLANcomponent === `Device.Bridging.Bridge.${sVLANBridgeIndex}`); + if (pvBridge) { + pvBridge.CVLANcomponents = pvBridge.CVLANcomponents + `,Device.Bridging.Bridge.${cVLANBridgeIndex}`; + return; + } + } + + providerBridges.push({ + Alias: `cpe-${dev.name}`, + Enable: 1, + Type: type, + SVLANcomponent: `Device.Bridging.Bridge.${sVLANBridgeIndex}`, + CVLANcomponents: `Device.Bridging.Bridge.${cVLANBridgeIndex}`, + _key: dev['.name'], + }); +} + +function addRegularEthernetPort(ethDevice, portIndex, briPorts) { + const tpid = getTPIDFromDeviceType(ethDevice.type, ethDevice.tpid); + + briPorts.push({ + Enable: 1, + Name: ethDevice['ifname'], + Alias: `cpe-${ethDevice['.name']}`, + TPID: tpid, + PVID: 1, + Type: 'VLANUnawarePort', + LowerLayers: `Device.Ethernet.Interface.${portIndex + 1}`, + _key: ethDevice['.name'], + }); +} + + +function handleVlanDevice(bridgeIndex, device, ethPorts, devices, bridges, briPorts, briVLAN, briVLANPort, providerBridges) { + let qinqDev; + let ethIndex = ethPorts.findIndex(x => device.ifname === x.ifname); + + if (device.type === '8021ad') { + if (ethIndex < 0) { + _log_error('base ethernet device not found', device.ifname); + return; + } + createProviderBridge(device, 'S-VLAN', ethIndex + 1, bridges, providerBridges, bridgeIndex); + return; + } + + if (ethIndex < 0) { + qinqDev = devices.find(x => x.name === device.ifname); + if (!qinqDev || !qinqDev.ifname) { + _log_error('device ifname not found', device.ifname); + return; + } + if (qinqDev.type !== '8021ad' || device.type !== '8021q') { + _log_error('invalid qinq device type', qinqDev['.name'], device['.name']); + return; + } + device.ifname = qinqDev.ifname; + ethIndex = ethPorts.findIndex(x => device.ifname === x.ifname); + if (ethIndex < 0) { + _log_error('base ethernet device not found', device.ifname); + return; + } + } + + if (device.type !== '8021q') { + _log_error('unsupported device type', device['.name'], device.type); + return; + } + + let vlanIndex = briVLAN.findIndex(x => Number(x.VLANID) === Number(device.vid)); + if (vlanIndex < 0) { + briVLAN.push({ Enable: 1, VLANID: Number(device.vid) }); + vlanIndex = briVLAN.length; + } else { + vlanIndex += 1; + } + + const portType = qinqDev ? 'CustomerEdgePort' : getBridgePortType(device.type); + const tpid = getTPIDFromDeviceType(device.type, device.tpid); + + briPorts.push({ + Enable: 1, + Name: device['ifname'], + Alias: `cpe-${device['.name']}`, + TPID: tpid, + PVID: device.pvid ? Number(device.vid): 1, + Type: portType, + LowerLayers: `Device.Ethernet.Interface.${ethIndex + 1}`, + _key: device['.name'], + }); + + briVLANPort.push({ + Enable: 1, + VLAN: `Device.Bridging.Bridge.${bridgeIndex}.VLAN.${vlanIndex}`, + Port: `Device.Bridging.Bridge.${bridgeIndex}.Port.${briPorts.length}`, + Untagged: device.untagged ? 1 : 0, + _key: device['.name'], + }); + + if (qinqDev && qinqDev.vid) { + createProviderBridge(qinqDev, 'PE', ethIndex + 1, bridges, providerBridges, bridgeIndex); + } +} + +function importBridge(dev, devices, bridges, bridgeVlans, providerBridges) { + const briPorts = []; + const briVLAN = []; + const briVLANPort = []; + + // create the management port first + briPorts.push({ + Alias: `cpe-${dev.name}`, + Enable: 1, + Name: dev.name, + ManagementPort: 1, + PVID: 1, + TPID: 37120, + Type: 'CustomerVLANPort', + }); + + bridges.push({ + Name: dev.name, + Alias: `cpe-${dev.name}`, + Enable: 1, + 'Port.': briPorts, + 'VLAN.': briVLAN, + 'VLANPort.': briVLANPort, + _key: dev['.name'], + }); + + const ethPorts = devices.filter(x => x.type === undefined && x.eee !== undefined); + + for (const portName of (dev.ports || [])) { + let device; + const portIndex = ethPorts.findIndex(x => x.ifname === portName); + const briVLANInfo = findPortFromBridgeVlan(bridgeVlans, portName); + if (portIndex >= 0 && !briVLANInfo) { + // Regular ethernet port + const ethDevice = ethPorts[portIndex]; + addRegularEthernetPort(ethDevice, portIndex, briPorts); + continue; + } + + if (briVLANInfo && portIndex >= 0) { + // bridge-vlan device + device = {['.name']: portName, ifname: portName, type: '8021q', name: portName, vid: Number(briVLANInfo[0]), untagged: !briVLANInfo[1], pvid: briVLANInfo[2]}; + } else { + // vlan device + device = devices.find(x => x.name === portName); + if (!device) { + // check if it is a valid sub-interface + if (isVLANSubInterface(portName, ethPorts)) { + const ifname = portName.split('.').slice(0, -1).join('.'); + const vid = portName.split('.').pop(); + device = {['.name']: portName, ifname: ifname, type: '8021q', name: portName, vid: Number(vid)}; + } else { + _log_error('device not found', portName); + return; + } + } + } + + if (!device.ifname || !device.vid) { + _log_error('ifname or vid not found', device['.name']); + return; + } + + handleVlanDevice(bridges.length, device, ethPorts, devices, bridges, briPorts, briVLAN, briVLANPort, providerBridges); + } + + if (briPorts.length > 1) { + const indexes = Array.from({ length: briPorts.length - 1 }, (v, i) => i + 2); + briPorts[0].LowerLayers = indexes.map(i => `Device.Bridging.Bridge.${bridges.length}.Port.${i}`).join(','); + } +} + +export function importDeviceBridgingBridge() { + const bridges = []; + const providerBridges = []; + const devices = getUciByType('network', 'device'); + const bridgeVlans = getUciByType('network', 'bridge-vlan'); + devices?.forEach(dev => { + if (dev.type === 'bridge') { + const bridgeVlan = bridgeVlans?.filter(x => x.device === dev.name); + importBridge(dev, devices, bridges, bridgeVlan, providerBridges); + } + }); + + if (providerBridges.length > 0) { + _dm_update('Device.Bridging.ProviderBridge.', providerBridges); + } + + return bridges; +} diff --git a/bridgemngr/dm/bridge.js b/bridgemngr/dm/bridge.js new file mode 100644 index 000000000..eed02cfb0 --- /dev/null +++ b/bridgemngr/dm/bridge.js @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2025 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +import * as std from 'std'; +import { isTrue } from '../utils.js'; +import { getUciByType } from '../uci.js'; + +function setMgmtPortLowerLayers(bri) { + if (!bri) return 0; + + const portPath = `Device.Bridging.Bridge.${bri['.index']}.Port.`; + const mgmtPort = _dm_instances(portPath, '(ManagementPort="true" OR ManagementPort=1)'); + if (mgmtPort.length !== 1) return 0; + + const nonMgmtPort = _dm_instances(portPath, '(ManagementPort="false" OR ManagementPort=0)'); + _dm_update(`${mgmtPort[0]}.LowerLayers`, nonMgmtPort.join(',')); + return 0; +} + +export function changedDeviceBridgingBridgePort(bri) { + return setMgmtPortLowerLayers(bri); +} + +export function changedDeviceBridgingBridgePortManagementPort(bri) { + return setMgmtPortLowerLayers(bri); +} + +export function getDeviceBridgingBridgeStatus(bri) { + const enable = _dm_get(`Device.Bridging.Bridge.${bri['.index']}.Enable`); + return enable ? 'Enabled' : 'Disabled'; +} + +export function getDeviceBridgingBridgeSTPStatus(bri) { + const stpState = std.loadFile(`/sys/class/net/${bri.Name}/bridge/stp_state`)?.trim(); + return stpState === '1' ? 'Enabled' : 'Disabled'; +} + +export function getDeviceBridgingBridgePortStatus(bri, port) { + if (!port['.db']) return 'Up'; + + const enable = _dm_get(`Device.Bridging.Bridge.${bri['.index']}.Port.${port['.index']}.Enable`); + return enable ? 'Up' : 'Down'; +} + +export function infoDeviceBridgingBridgePort(path, port) { + const mgmtPort = _dm_get(`${path}.ManagementPort`); + if (typeof mgmtPort === 'undefined' || mgmtPort) return; + + const lower = _dm_get(`${path}.LowerLayers`); + if (lower) { + port.ifname = _dm_linker_value(lower); + } +} + +// Helper function to read network statistics +function getNetworkStat(port, statName) { + return std.loadFile(`/sys/class/net/${port.ifname}/statistics/${statName}`)?.trim(); +} + +export const getDeviceBridgingBridgePortStatsBytesSent = (bri, port) => + getNetworkStat(port, 'tx_bytes'); + +export const getDeviceBridgingBridgePortStatsBytesReceived = (bri, port) => + getNetworkStat(port, 'rx_bytes'); + +export const getDeviceBridgingBridgePortStatsPacketsSent = (bri, port) => + getNetworkStat(port, 'tx_packets'); + +export const getDeviceBridgingBridgePortStatsPacketsReceived = (bri, port) => + getNetworkStat(port, 'rx_packets'); + +export const getDeviceBridgingBridgePortStatsErrorsSent = (bri, port) => + getNetworkStat(port, 'tx_errors'); + +export const getDeviceBridgingBridgePortStatsErrorsReceived = (bri, port) => + getNetworkStat(port, 'rx_errors'); + +export const getDeviceBridgingBridgePortStatsDiscardPacketsSent = (bri, port) => + getNetworkStat(port, 'tx_dropped'); + +export const getDeviceBridgingBridgePortStatsDiscardPacketsReceived = (bri, port) => + getNetworkStat(port, 'rx_dropped'); + +export const getDeviceBridgingBridgePortStatsMulticastPacketsReceived = (bri, port) => + getNetworkStat(port, 'multicast'); + +export const getDeviceBridgingBridgePortStatsUnicastPacketsSent = (bri, port) => + getNetworkStat(port, 'tx_unicast_packets'); + +export const getDeviceBridgingBridgePortStatsUnicastPacketsReceived = (bri, port) => + getNetworkStat(port, 'rx_unicast_packets'); + +export const getDeviceBridgingBridgePortStatsMulticastPacketsSent = (bri, port) => + getNetworkStat(port, 'tx_multicast_packets'); + +export const getDeviceBridgingBridgePortStatsBroadcastPacketsSent = (bri, port) => + getNetworkStat(port, 'tx_broadcast_packets'); + +export const getDeviceBridgingBridgePortStatsBroadcastPacketsReceived = (bri, port) => + getNetworkStat(port, 'rx_broadcast_packets'); + +export const getDeviceBridgingBridgePortStatsUnknownProtoPacketsReceived = (bri, port) => + getNetworkStat(port, 'rx_unknown_packets'); + +export function getDeviceBridgingBridgePort(bri) { + const networkName = bri.Name.startsWith('br-') ? bri.Name.slice(3) : bri.Name; + + const wifiIfaces = getUciByType('wireless', 'wifi-iface', { match: { multi_ap: '2', network: networkName } }); + wifiIfaces?.forEach(x => { + const ssid = getUciByType('WiFi', 'SSID', + { match: { device: x.device, ssid: x.ssid}, confdir: '/etc/bbfdm/dmmap'}); + if (Array.isArray(ssid) && ssid[0].__instance__) { + x.ssidPath = `Device.WiFi.SSID.${ssid[0].__instance__}`; + } + }); + + return wifiIfaces; +} + +export function setDeviceBridgingBridgePortManagementPort(val, bri, port) { + if (isTrue(val)) { + _db_set(`Device.Bridging.Bridge.${bri['.index']}.Port.${port['.index']}.Name`, bri.Name); + } + return 1; +} diff --git a/bridgemngr/dm/common.js b/bridgemngr/dm/common.js new file mode 100755 index 000000000..98d1c9f11 --- /dev/null +++ b/bridgemngr/dm/common.js @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +export const bridgePortTypeMap = [ + { portType: 'ProviderNetworkPort', devType: '8021ad' }, + { portType: 'CustomerVLANPort', devType: '8021q' }, + { portType: 'CustomerEdgePort', devType: '8021q' }, + { portType: 'CustomerNetworkPort', devType: '8021q' }, + { portType: 'VLANUnawarePort', devType: '' } +]; + +export function getBridgePortType(devType) { + const mapping = bridgePortTypeMap.find(map => map.devType === devType); + return mapping ? mapping.portType : null; +} + +export function getBridgeDeviceType(portType) { + const mapping = bridgePortTypeMap.find(map => map.portType === portType); + return mapping ? mapping.devType : ''; +} + +export function getDefaultTPID(deviceType) { + switch (deviceType) { + case '8021q': + return '33024'; + case '8021ad': + return '34984'; + default: + return '37120'; + } +} + +export function getTPIDFromDeviceType(deviceType, explicitTPID) { + // If explicit TPID is set, use it + if (explicitTPID && explicitTPID !== '') { + return parseInt(explicitTPID, 10); + } + + // Default TPID based on device type + switch (deviceType) { + case '8021q': + return 33024; + case '8021ad': + return 34984; + default: + return 37120; + } +} diff --git a/dm-framework/Makefile b/dm-framework/Makefile new file mode 100755 index 000000000..6c2934acc --- /dev/null +++ b/dm-framework/Makefile @@ -0,0 +1,170 @@ +# +# Copyright (c) 2023 Genexis B.V. All rights reserved. +# This Software and its content are protected by the Dutch Copyright Act +# ('Auteurswet'). All and any copying and distribution of the software +# and its content without authorization by Genexis B.V. is +# prohibited. The prohibition includes every form of reproduction and +# distribution. +# +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=dm-framework +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://dev.iopsys.eu/lcm/dm-framework.git +PKG_SOURCE_VERSION:=ba2ec403f08cc0d5401a1610517f17490c94a978 +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz +PKG_MIRROR_HASH:=skip + +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) + +# Build directories for each component +DMAPI_BUILD_DIR:=$(PKG_BUILD_DIR)/dm-api-build +DMAGENT_BUILD_DIR:=$(PKG_BUILD_DIR)/dm-agent-build + +include $(INCLUDE_DIR)/package.mk +include ../bbfdm/bbfdm.mk + +# +# DM-API Package Definition +# +define Package/dm-api + CATEGORY:=Genexis + TITLE:=dm-api + DEPENDS:=+libsqlite3 \ + +libjson-c +libstdcpp +quickjs \ + +libubus +libubox +libuci + URL:=http://www.genexis.eu + PKG_LICENSE:=GENEXIS + PKG_LICENSE_URL:= +endef + +define Package/dm-api/description + This package contains api for the dm-framework +endef + +# +# DM-Agent Package Definition +# +define Package/dm-agent + DEPENDS:=+dm-api +libubox +libubus +ubus + CATEGORY:=Genexis + TITLE:=dm-framework agent + URL:=http://www.genexis.eu + PKG_LICENSE:=GENEXIS + PKG_LICENSE_URL:= +endef + +define Package/dm-agent/description + This package contains dm-framework agent. +endef + +# +# Build Preparation +# +define Build/Prepare + $(call Build/Prepare/Default) + + # Prepare dm-api + mkdir -p $(DMAPI_BUILD_DIR) + $(CP) -rf $(PKG_BUILD_DIR)/dm-api/* $(DMAPI_BUILD_DIR)/ + + # Prepare dm-agent + mkdir -p $(DMAGENT_BUILD_DIR) + $(CP) -rf $(PKG_BUILD_DIR)/dm-agent/* $(DMAGENT_BUILD_DIR)/ +endef + +TARGET_CFLAGS += $(FPIC) + +# +# Build Compilation +# +define Build/Compile + # Build dm-api + $(MAKE) -C $(DMAPI_BUILD_DIR)\ + PROJECT_ROOT="$(DMAPI_BUILD_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + EXTRA_CFLAGS="$(TARGET_CFLAGS) -I$(DMAPI_BUILD_DIR)" \ + all + + # Build dm-agent (depends on dm-api) + $(MAKE) -C $(DMAGENT_BUILD_DIR)\ + PROJECT_ROOT="$(DMAGENT_BUILD_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + EXTRA_CFLAGS="$(TARGET_CFLAGS) -I$(DMAGENT_BUILD_DIR)" \ + all +endef + +# +# Development Installation (headers and libraries) +# +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include + $(INSTALL_DIR) $(1)/usr/lib + + # DM-API development files - headers are now in dm-api/include/ + $(CP) $(DMAPI_BUILD_DIR)/include/dm_types.h $(1)/usr/include/ + $(CP) $(DMAPI_BUILD_DIR)/include/dm_node.h $(1)/usr/include/ + $(CP) $(DMAPI_BUILD_DIR)/core/dm_api.h $(1)/usr/include/ + $(CP) $(DMAPI_BUILD_DIR)/core/dm_linker.h $(1)/usr/include/ + $(CP) $(DMAPI_BUILD_DIR)/core/dbmgr.h $(1)/usr/include/ + $(CP) $(DMAPI_BUILD_DIR)/include/dm_log.h $(1)/usr/include/ + $(CP) $(DMAPI_BUILD_DIR)/utils/dm_list.h $(1)/usr/include/ + $(CP) $(DMAPI_BUILD_DIR)/libdmapi.so $(1)/usr/lib/ + + # Install json2code.js script and package.json to staging for other packages to use + $(INSTALL_DIR) $(1)/usr/lib/dm-framework/scripts + $(CP) $(PKG_BUILD_DIR)/scripts/json2code.js $(1)/usr/lib/dm-framework/scripts/ + $(CP) $(PKG_BUILD_DIR)/scripts/package.json $(1)/usr/lib/dm-framework/scripts/ +endef + +# +# Package Installation - DM-API +# +define Package/dm-api/install + $(INSTALL_DIR) $(1)/usr/lib + $(INSTALL_DIR) $(1)/sbin/ + $(INSTALL_DIR) $(1)/etc/bbfdm/dmf + + $(INSTALL_BIN) $(DMAPI_BUILD_DIR)/libdmapi.so $(1)/usr/lib/ + $(CP) $(DMAPI_BUILD_DIR)/quickjs/uci.js $(1)/etc/bbfdm/dmf/ + $(CP) $(DMAPI_BUILD_DIR)/quickjs/utils.js $(1)/etc/bbfdm/dmf/ +endef + +# +# Package Installation - DM-Agent +# +define Package/dm-agent/install + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_DIR) $(1)/etc/config + $(BBFDM_REGISTER_SERVICES) ./dmf_service.json $(1) $(PKG_NAME) + $(INSTALL_BIN) $(DMAGENT_BUILD_DIR)/dm-agent $(1)/usr/sbin +endef + +define Package/dm-framework + CATEGORY:=Genexis + TITLE:=DM Framework Meta Package + DEPENDS:=+dm-api +dm-agent + URL:=http://www.genexis.eu + PKG_LICENSE:=GENEXIS +endef + +define Package/dm-framework/description + This is a meta package that pulls in dm-api and dm-agent. +endef + +define Package/dm-framework/install + true +endef + +# Register all three packages +$(eval $(call BuildPackage,dm-api)) +$(eval $(call BuildPackage,dm-agent)) +$(eval $(call BuildPackage,dm-framework)) diff --git a/dm-framework/dm-framework.mk b/dm-framework/dm-framework.mk new file mode 100644 index 000000000..8cdd74cf0 --- /dev/null +++ b/dm-framework/dm-framework.mk @@ -0,0 +1,33 @@ +# dm-framework.mk - Common rules for DM Framework + +DM_SCRIPT_DIR ?= $(STAGING_DIR)/usr/lib/dm-framework/scripts +JSON2CODE = $(DM_SCRIPT_DIR)/json2code.js + +# Macro to generate code +# $(1): Input directory (datamodels) +# $(2): Output directory (where generated files go) +# $(3): Vendor Prefix (optional) +define Build/Compile/DM + $(INSTALL_DIR) $(2) + @# Install npm dependencies if not already installed + @if [ ! -d "$(DM_SCRIPT_DIR)/node_modules" ]; then \ + cd $(DM_SCRIPT_DIR) && npm install --production; \ + fi + node $(JSON2CODE) -i $(1) -o $(2) $(if $(3),--vendor-prefix $(3)) + $(TARGET_CC) $(TARGET_CFLAGS) -I$(2) -I$(STAGING_DIR)/usr/include/ -fPIC -c $(2)/dm.c -o $(2)/dm.o + $(TARGET_CC) $(TARGET_LDFLAGS) -shared -o $(2)/lib$(PKG_NAME).so $(2)/dm.o +endef + +# Macro to install DM +# $(1): Input directory (datamodels) +# $(2): Output directory (build dir) +# $(3): Destination directory (rootfs) +# $(4): Package Name (subdir in /etc/bbfdm/dmf) +define Build/Install/DM + $(INSTALL_DIR) $(3)/etc/bbfdm/dmf/$(4) + $(CP) $(2)/lib$(PKG_NAME).so $(3)/etc/bbfdm/dmf/$(4)/ + $(CP) $(1)/*.js $(3)/etc/bbfdm/dmf/$(4)/ + $(CP) $(2)/default.db $(3)/etc/bbfdm/dmf/default_dm.db + $(CP) $(2)/exports.js $(3)/etc/bbfdm/dmf/$(4)/exports.js + $(CP) $(2)/dm_consts.js $(3)/etc/bbfdm/dmf/$(4)/dm_consts.js +endef diff --git a/dm-framework/dmf_service.json b/dm-framework/dmf_service.json new file mode 100644 index 000000000..e5ecfde04 --- /dev/null +++ b/dm-framework/dmf_service.json @@ -0,0 +1,17 @@ +{ + "daemon": { + "enable": "1", + "service_name": "dmf", + "dm-framework": true, + "unified_daemon": false, + "services": [ + { + "parent_dm": "Device.", + "object": "Bridging" + } + ], + "config": { + "loglevel": "3" + } + } +} diff --git a/quickjs/Makefile b/quickjs/Makefile index fd9afd4de..d5388785d 100644 --- a/quickjs/Makefile +++ b/quickjs/Makefile @@ -35,6 +35,10 @@ MAKE_FLAGS = \ EXTRA_LIBS="-latomic" \ CROSS_PREFIX="$(TARGET_CROSS)" +# Ensure the static library is built with position independent code so it can +# be linked into shared objects. +TARGET_CFLAGS += -fPIC + define Build/Compile # The upstream Makefile uses the same CFLAGS for host and target builds, # which breaks cross-compilation. We work around this by first building