--- /dev/null
+#!/bin/sh
+
+. /lib/functions.sh
+. /lib/functions/network.sh
+
+switch_names=""
+
+warn() {
+ echo "$@" >&2
+}
+
+clear_port_vlans() {
+ local port=$1
+ local self=$2
+ local vlans=$(bridge vlan show dev "$port" | sed -ne 's#^[^ ]* \+\([0-9]\+\).*$#\1#p')
+
+ local vlan
+ for vlan in $vlans; do
+ bridge vlan del vid "$vlan" dev "$port" $self
+ done
+}
+
+lookup_switch() {
+ local cfg=$1
+ local swname
+
+ config_get swname "$cfg" switch
+
+ # Auto-determine switch if not specified ...
+ if [ -z "$swname" ]; then
+ case "$switch_names" in
+ *\ *)
+ warn "VLAN section '$cfg' does not specify a switch but multiple switches present, using first one"
+ swname=${switch_names%% *}
+ ;;
+ *)
+ swname=${switch_names}
+ ;;
+ esac
+
+ # ... otherwise check if the referenced switch is declared
+ else
+ case " $switch_names " in
+ *" $swname "*) : ;;
+ *)
+ warn "Switch '$swname' specified by VLAN section '$cfg' does not exist"
+ return 1
+ ;;
+ esac
+ fi
+
+ export -n "switch=$swname"
+}
+
+validate_vid() {
+ local vid=$1
+
+ case "$vid" in
+ [1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-4][0-9][0-9][0-9])
+ if [ $vid -gt 4096 ]; then
+ return 1
+ fi
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+
+ return 0
+}
+
+setup_switch() {
+ local cfg=$1
+ local cpu
+
+ # Read configured CPU port from uci ...
+ config_get cpu "$cfg" cpu_port
+
+ # ... if unspecified, find first CPU port
+ if [ -z "$cpu" ]; then
+ local e
+ for e in /sys/class/net/*/dsa; do
+ if [ -d "$e" ]; then
+ cpu=${e%/dsa}
+ cpu=${cpu##*/}
+ break
+ fi
+ done
+ fi
+
+ # Bail out if we cannot determine the CPU port
+ if [ -z "$cpu" ]; then
+ warn "Unable to determine CPU port for switch '$cfg'"
+ return 1
+ fi
+
+ append switch_names "$cfg"
+
+ # Prevent netifd from picking up our switch bridge just yet
+ network_defer_device "$cfg"
+
+ # Increase MTU of CPU port to 1508 to accomodate for VID + DSA tag
+ ip link set "$cpu" mtu 1508
+ ip link set "$cpu" up
+
+ # (Re)create switch bridge device in case it is not yet set up
+ local filtering=$(cat "/sys/class/net/$cfg/bridge/vlan_filtering" 2>/dev/null)
+ if [ ${filtering:-0} != 1 ]; then
+ ip link set "$cfg" down 2>/dev/null
+ ip link delete dev "$cfg" 2>/dev/null
+ ip link add name "$cfg" type bridge
+ echo 1 > "/sys/class/net/$cfg/bridge/vlan_filtering"
+ fi
+
+ ip link set "$cfg" up
+
+ # Unbridge DSA ports and flush any VLAN filters on them, they're added back later
+ local port
+ for port in /sys/class/net/*"/upper_${cfg}"; do
+ if [ -e "$port" ]; then
+ port=${port%/upper_*}
+ port=${port##*/}
+
+ ip link set "$port" nomaster
+
+ # Unbridging the port should already clear VLANs, but be safe
+ clear_port_vlans "$port"
+ fi
+ done
+
+ # Clear any VLANs on the switch bridge, they're added back later
+ clear_port_vlans "$cfg" self
+}
+
+setup_switch_vlan() {
+ local cfg=$1
+ local switch vlan ports
+
+ config_get switch "$cfg" switch
+ config_get vlan "$cfg" vlan
+ config_get ports "$cfg" ports
+
+ lookup_switch "$cfg" || return 1
+ validate_vid "$vlan" || {
+ warn "VLAN section '$cfg' specifies an invalid VLAN ID '$vlan'"
+ return 1
+ }
+
+ # Setup ports
+ local port tag pvid
+ for port in $ports; do
+ tag=${port#*.}
+ port=${port%.*}
+ pvid=
+
+ if [ "$tag" != "$port" ] && [ "$tag" = t ]; then
+ tag=tagged
+ else
+ tag=untagged
+ fi
+
+ # Add the port to the switch bridge and delete the default
+ # VLAN 1 if it is not yet joined to the switch.
+ if [ ! -e "/sys/class/net/$port/upper_$switch" ]; then
+ ip link set dev "$port" up
+ ip link set dev "$port" master "$switch"
+
+ # Get rid of default VLAN 1
+ bridge vlan del vid 1 dev "$port"
+ fi
+
+ # Promote the first untagged VLAN of this port to the PVID
+ if [ "$tag" = untagged ] && ! bridge vlan show dev "$port" | grep -qi pvid; then
+ pvid=pvid
+ fi
+
+ # Add VLAN filter entry for port
+ bridge vlan add dev "$port" vid $vlan $pvid $tag
+ done
+
+ # Make the switch bridge itself handle the VLAN as well
+ bridge vlan add dev "$switch" self vid $vlan tagged
+}
+
+setup_switch_port() {
+ local cfg=$1
+ local switch port pvid tag
+
+ config_get port "$cfg" port
+ config_get pvid "$cfg" pvid
+
+ lookup_switch "$cfg" || return 1
+ validate_vid "$pvid" || {
+ warn "Port section '$cfg' specifies an invalid PVID '$pvid'"
+ return 1
+ }
+
+ # Disallow setting the PVID of the switch bridge itself
+ [ "$port" != "$switch" ] || {
+ warn "Port section '$cfg' must not change PVID of the switch bridge"
+ return 1
+ }
+
+ # Determine existing VLAN config
+ local vlanspec=$(bridge vlan show dev "$port" vid "$pvid" 2>/dev/null | sed -ne2p)
+ echo "$vlanspec" | grep -qi untagged && tag=untagged || tag=tagged
+
+ bridge vlan add vid "$pvid" dev "$port" pvid $tag
+}
+
+apply_config() {
+ config_load network
+ config_foreach setup_switch dsa
+
+ # If no switch is explicitely declared, synthesize switch0
+ if [ -z "$switch_names" ] && ! setup_switch switch0; then
+ warn "No DSA switches found"
+ return 1
+ fi
+
+ config_foreach setup_switch_vlan dsa_vlan
+ config_foreach setup_switch_port dsa_port
+
+ # Ready switch bridge devices
+ local switch
+ for switch in $switch_names; do
+ network_ready_device "$switch"
+ done
+}
+
+show_switch() {
+ local switch=$1
+
+ printf "Switch: %s\n" "$switch"
+ printf "VLAN/"
+
+ local port ports
+ for port in "/sys/class/net/$switch/lower_"*; do
+ port=${port##*/lower_}
+
+
+ printf " | %-5s" "$port"
+ append ports "$port"
+ done
+
+ printf " |\nLink:"
+
+ for port in $ports; do
+ local carrier=$(cat "/sys/class/net/$port/carrier")
+ local duplex=$(cat "/sys/class/net/$port/duplex")
+ local speed=$(cat "/sys/class/net/$port/speed")
+
+ if [ ${carrier:-0} -eq 0 ]; then
+ printf " | %-5s" "down"
+ else
+ [ "$duplex" = "full" ] && duplex=F || duplex=H
+ printf " | %4d%s" "$speed" "$duplex"
+ fi
+ done
+
+ local vlans=$(bridge vlan show dev "$switch" | sed -ne 's#^[^ ]* \+\([0-9]\+\).*$#\1#p')
+ local vlan
+ for vlan in $vlans; do
+ printf " |\n%4d " "$vlan"
+
+ for port in $ports; do
+ local pvid="" utag="" word
+ for word in $(bridge vlan show dev "$port" vid "$vlan"); do
+ case "$word" in
+ PVID) pvid="*" ;;
+ "$vlan") utag="t" ;;
+ Untagged) utag="u" ;;
+ esac
+ done
+
+ printf " | %-2s " "$utag$pvid"
+ done
+ done
+
+ printf " |\n\n"
+}
+
+add_switch() {
+ append switch_names "$1"
+}
+
+show_config() {
+ config_load network
+ config_foreach add_switch dsa
+
+ local switch
+ for switch in ${switch_names:-switch0}; do
+ show_switch "$switch"
+ done
+}
+
+
+case "$1" in
+ show) show_config ;;
+ apply) apply_config ;;
+ *)
+ echo "Usage: ${0##*/} show"
+ echo " ${0##*/} apply"
+ exit 1
+ ;;
+esac