]> git.ipfire.org Git - people/ms/mstpd.git/commitdiff
Broadcom Xstrata support
authordv1tas <dv1tas@fbe50366-0c72-4402-a84b-5d246361dba7>
Wed, 1 Aug 2012 08:37:42 +0000 (08:37 +0000)
committerdv1tas <dv1tas@fbe50366-0c72-4402-a84b-5d246361dba7>
Wed, 1 Aug 2012 08:37:42 +0000 (08:37 +0000)
Signed-off-by: Vladimir Cotfas <unix_router@yahoo.com>
git-svn-id: svn://svn.code.sf.net/p/mstpd/code/trunk@34 fbe50366-0c72-4402-a84b-5d246361dba7

broadcom_xstrata/README [new file with mode: 0644]
broadcom_xstrata/bcmexp.c [new file with mode: 0644]
broadcom_xstrata/driver_deps.c [new file with mode: 0644]
broadcom_xstrata/etc-init.d-stp.sh [new file with mode: 0644]
broadcom_xstrata/knet.soc [new file with mode: 0644]
broadcom_xstrata/stp.soc [new file with mode: 0644]

diff --git a/broadcom_xstrata/README b/broadcom_xstrata/README
new file mode 100644 (file)
index 0000000..2b51d20
--- /dev/null
@@ -0,0 +1,39 @@
+Synopsis:
+
+This directory contains example implementation of driver_deps.c for the 
+Broadcom Xstrata as well as some configuration tools.
+
+Author: Vladimir Cotfas <unix_router@yahoo.com>
+
+Description:
+
+Broadcom on Xstrata has a Control Plane MIPS CPU that can configure/control 
+the switch. They have a few kernel modules for passing commands to the 
+hardware and a "knet" multiplex pseudo-Ethernet module. The bulk of their 
+logic is in a user-mode application named "bcm.user". There is a CLI for it 
+called "bcm" but it's rather stupid and needs to run on a pty. I wrote 
+bcmexp.c as a means of feeding CLI commands to bcm/bcm.user.
+
+Typically one sets up one pseudo-Ethernet interface per physical port via 
+knet. The file knet.soc shows our arrangement for the two upstream ports 
+(known to Linux as GbE1 and GbE1 and to BCM as ge3 and ge4 respectively). 
+This .soc file is injected into BCM at startup via some Broadcom scripts. The 
+thing to notice is the priority and the "destid" which is the numeric ID of 
+the interface as used by knet. This will be used later.
+
+We also have "eth0" which is a sink for both gig ports. As gathered from the 
+soc file no packets actually reach GbE1/GbE2 from the switch fabric in normal 
+operation.
+
+Then stp.soc comes into play: it configures the switch fabric to except STP 
+BPDUs to the control plane as "fp" rule "31". Then comes two lines of knet 
+configuration that direct the excepted packets into GbE1 and GbE2 based on 
+ingress ports. Note the destid. This soc file is loaded into BCM when mstpd 
+starts in the startup script stp.sh. Both gig ports are switched to Blocking 
+state.
+
+The startup script puts both gig ports into a bridge and goes through the 
+motions of starting mstpd.
+
+The last bit released as per GPL is driver_deps.c which sends commands to BCM 
+to switch port states.
\ No newline at end of file
diff --git a/broadcom_xstrata/bcmexp.c b/broadcom_xstrata/bcmexp.c
new file mode 100644 (file)
index 0000000..07b57ab
--- /dev/null
@@ -0,0 +1,467 @@
+/*
+ * Based on Bill Evans's getmale.c
+ *
+ * Copyright (C) 2012 by Vladimir Cotfas <unix_router@yahoo.com>
+ *
+ * Released under GPLv2
+ */
+#include <sys/select.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <pty.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <utmp.h>
+#include <ctype.h>
+#include <pty.h>
+
+#define MPROMPT "BCM.0> "
+
+int pty_phyle;
+
+char recent_input[8];
+
+struct termios tp1;
+struct termios tp2;
+
+int at_main_prompt(void);
+
+void log_pty_data(int the_character);
+
+int send_command(char *the_string, int add_cr, int expect_prompt);
+
+int send_constant(char *the_string);
+
+int wait_for_main_prompt(int bell);
+
+int wait_for_one_character(void);
+
+int wait_for_password_prompt(int bell);
+
+int wait_for_string(char *the_string, int bell);
+
+void wait_for_zero_status(int bell);
+
+int at_main_prompt(void)
+{
+//  printf("[%s]\b", recent_input); fflush(stdout);
+  return strstr(recent_input, MPROMPT) != NULL;
+}                              /* at_main_prompt() */
+
+char outbuf[81920] = {0};
+
+void p_printable(const char* buf, const int len)
+{
+  if(buf == NULL || len < 1) return;
+
+  int i;
+  for(i = 0; i < len; i++) {
+    if(buf[i] == '\n' || isprint(buf[i])) {
+      strncat(outbuf, (buf+i), 1);
+      if(buf[i] == '\n') {
+       do {
+         if(!strncmp(outbuf, "[H[J", 4)) continue;
+          if(!strncmp(outbuf, MPROMPT, strlen(MPROMPT))) continue;
+          write(STDOUT_FILENO, outbuf, strlen(outbuf));
+        } while(0);
+       outbuf[0] = '\0';
+      }
+    }
+  }
+}
+
+void log_pty_data(int the_character)
+{
+  int jndex;
+
+  char the_buffer[4];
+
+  the_buffer[0] = the_character;
+
+  p_printable(the_buffer, 1);
+  //write(STDOUT_FILENO, the_buffer, 1);
+
+  for (jndex = 0; jndex < sizeof(recent_input) - 1; jndex++) {
+    recent_input[jndex] = recent_input[jndex + 1];
+  }
+
+  recent_input[jndex] = the_buffer[0];
+
+}                              /* log_pty_data() */
+
+int send_command(char *the_string, int add_cr, int expect_prompt)
+{
+  int function_result;
+
+  function_result = send_constant(the_string);
+
+  if (function_result < 1) {
+    return function_result;
+  }
+
+  if (add_cr) {
+    function_result = send_constant("\r");
+
+    if (function_result < 1) {
+      return function_result;
+    }
+  }
+
+  if (expect_prompt) {
+    function_result = wait_for_one_character();
+
+    if (function_result < 1) {
+      return function_result;
+    }
+
+    function_result = wait_for_main_prompt(expect_prompt - 1);
+
+    if (function_result < 1) {
+      return function_result;
+    }
+  }
+
+  return function_result;
+
+}                              /* send_command() */
+
+int send_constant(char *the_string)
+{
+  int input_index;
+  int max_fd;
+  int select_result;
+
+  char the_buffer[4];
+
+  ssize_t read_result;
+
+  struct timeval timeout;
+
+  fd_set read_fds;
+  fd_set write_fds;
+
+  input_index = 0;
+
+  for (;;) {
+    FD_ZERO(&read_fds);
+    if (0) {
+      FD_SET(pty_phyle, &read_fds);
+    }
+
+    FD_ZERO(&write_fds);
+    FD_SET(pty_phyle, &write_fds);
+
+    max_fd = pty_phyle;
+
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+
+    select_result = select(max_fd + 1, &read_fds, &write_fds, NULL, &timeout);
+
+    if (select_result < 1) {
+      if (0) {
+       the_buffer[0] = '.';
+
+       //write(STDOUT_FILENO, the_buffer, 1);
+       p_printable(the_buffer, 1);
+      }
+
+      continue;                        /* <--------- */
+    }
+
+    if (FD_ISSET(pty_phyle, &read_fds)) {
+      read_result = read(pty_phyle, the_buffer, 1);
+
+      if (read_result < 0) {
+       return -1;              /* <--------- */
+      }
+
+      if (read_result == 0) {
+       fprintf(stderr, "read(pty) returned 0\n");
+
+       return 0;
+      }
+
+      log_pty_data(the_buffer[0]);
+    } else if (FD_ISSET(pty_phyle, &write_fds)) {
+      if (the_string[input_index] == 0) {
+       return 1;               /* <--------- */
+      }
+
+      the_buffer[0] = the_string[input_index++];
+
+      write(pty_phyle, the_buffer, 1);
+    }
+  }
+
+}                              /* send_constant() */
+
+int wait_for_main_prompt(int bell)
+{
+  int max_fd;
+  int select_result;
+
+  char the_buffer[4];
+
+  ssize_t read_result;
+
+  struct timeval timeout;
+
+  fd_set read_fds;
+
+  for (;;) {
+    FD_ZERO(&read_fds);
+    FD_SET(pty_phyle, &read_fds);
+
+    max_fd = pty_phyle;
+
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+
+    select_result = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
+
+    if (select_result < 1) {
+      if (0) {
+       the_buffer[0] = '.';
+
+       //write(STDOUT_FILENO, the_buffer, 1);
+       p_printable(the_buffer, 1);
+      }
+
+      continue;                        /* <--------- */
+    }
+
+    if (FD_ISSET(pty_phyle, &read_fds)) {
+      read_result = read(pty_phyle, the_buffer, 1);
+
+      if (read_result < 0) {
+       return -1;              /* <--------- */
+      }
+
+      if (read_result == 0) {
+       fprintf(stderr, "read(pty) returned 0\n");
+
+       return 0;
+      }
+
+      log_pty_data(the_buffer[0]);
+
+      if (at_main_prompt()) {
+       if (bell) {
+         the_buffer[0] = 7;
+
+         //write(STDOUT_FILENO, the_buffer, 1);
+         p_printable(the_buffer, 1);
+       }
+
+       return 1;
+      }
+    }
+  }
+
+}                              /* wait_for_main_prompt() */
+
+int wait_for_one_character(void)
+{
+  int max_fd;
+  int select_result;
+
+  char the_buffer[4];
+
+  ssize_t read_result;
+
+  struct timeval timeout;
+
+  fd_set read_fds;
+
+  for (;;) {
+    FD_ZERO(&read_fds);
+    FD_SET(pty_phyle, &read_fds);
+
+    max_fd = pty_phyle;
+
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+
+    select_result = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
+
+    if (select_result < 1) {
+      if (0) {
+       the_buffer[0] = '.';
+
+       //write(STDOUT_FILENO, the_buffer, 1);
+       p_printable(the_buffer, 1);
+      }
+
+      continue;                        /* <--------- */
+    }
+
+    if (FD_ISSET(pty_phyle, &read_fds)) {
+      read_result = read(pty_phyle, the_buffer, 1);
+
+      if (read_result < 0) {
+       return -1;              /* <--------- */
+      }
+
+      if (read_result == 0) {
+       fprintf(stderr, "read(pty) returned 0\n");
+
+       return 0;
+      }
+
+      log_pty_data(the_buffer[0]);
+
+      return 1;
+    }
+  }
+}                              /* wait_for_one_character() */
+
+int wait_for_string(char *the_string, int bell)
+{
+  int found_it;
+  int jndex;
+  int kndex;
+  int max_fd;
+  int select_result;
+
+  char the_buffer[4];
+
+  ssize_t read_result;
+
+  struct timeval timeout;
+
+  fd_set read_fds;
+
+  for (;;) {
+    FD_ZERO(&read_fds);
+    FD_SET(pty_phyle, &read_fds);
+
+    max_fd = pty_phyle;
+
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+
+    select_result = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
+
+    if (select_result < 1) {
+      if (0) {
+       the_buffer[0] = '.';
+
+       //write(STDOUT_FILENO, the_buffer, 1);
+       p_printable(the_buffer, 1);
+      }
+
+      continue;                        /* <--------- */
+    }
+
+    if (FD_ISSET(pty_phyle, &read_fds)) {
+      read_result = read(pty_phyle, the_buffer, 1);
+
+      if (read_result < 0) {
+       return -1;              /* <--------- */
+      }
+
+      if (read_result == 0) {
+       fprintf(stderr, "read(pty) returned 0\n");
+
+       return 0;
+      }
+
+      log_pty_data(the_buffer[0]);
+
+      found_it = 1;
+
+      jndex = sizeof(recent_input) - 1;
+      kndex = strlen(the_string) - 1;
+
+      for (;;) {
+       if ((jndex < 0) || (kndex < 0)) {
+         break;                /* <--------- */
+       }
+
+       if (recent_input[jndex] != the_string[kndex]) {
+         found_it = 0;
+
+         break;                /* <--------- */
+       }
+
+       jndex--;
+       kndex--;
+      }
+
+      if (found_it) {
+       if (bell) {
+         the_buffer[0] = 7;
+
+         //write(STDOUT_FILENO, the_buffer, 1);
+         p_printable(the_buffer, 1);
+       }
+
+       return 1;
+      }
+    }
+  }
+
+}                              /* wait_for_string() */
+
+/*--------------------------------------------------------------------------*/
+
+int main(int argc, char* argv[])
+{
+  int i;
+  pid_t the_child;
+  struct winsize window_size;
+
+  if(argc < 2) return 0;
+
+  char cmd[256] = {0};
+  for(i = 1; i < argc; i++) {
+       if(i != 1) strncat(cmd, " ", 255);
+       strncat(cmd, argv[i], 255);
+  }
+  strncat(cmd, "\n", 255);
+       
+  recent_input[sizeof(recent_input) - 1] = 0;
+
+  ioctl(STDIN_FILENO, TIOCGWINSZ, &window_size);
+
+  the_child = forkpty(&pty_phyle, NULL, NULL, &window_size);
+
+  if (the_child == 0) {
+    execl("/bin/bcm", "bcm", NULL);
+
+    exit(0);
+  }
+
+  tcgetattr(0, &tp1);
+
+  tp2 = tp1;
+
+  tp2.c_iflag &= ~ICRNL;
+  tp2.c_lflag &= ~ICANON;
+  tp2.c_lflag &= ~ECHO;
+  tp2.c_cc[VMIN] = 1;
+  tp2.c_cc[VTIME] = 0;
+  tp2.c_cc[VINTR] = 0xFF;
+  tp2.c_cc[VSUSP] = 0xFF;
+  tp2.c_cc[VQUIT] = 0xFF;
+
+  if (tcsetattr(0, TCSANOW, &tp2) != 0) {
+    fprintf(stderr, "setting attributes failed\n");
+  }
+
+  //sleep(1);
+
+  wait_for_main_prompt(0);
+
+  send_constant(cmd);
+  wait_for_string(MPROMPT,0);
+  wait_for_string(MPROMPT,0);
+
+  tcsetattr(0, TCSANOW, &tp1);
+
+  return 0;
+}
diff --git a/broadcom_xstrata/driver_deps.c b/broadcom_xstrata/driver_deps.c
new file mode 100644 (file)
index 0000000..e72e9e3
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * driver_deps.c       Driver-specific code.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU General Public License
+ *  as published by the Free Software Foundation; either version
+ *  2 of the License, or (at your option) any later version.
+ *
+ * Authors: Vitalii Demianets <vitas@nppfactor.kiev.ua>
+ * Authors: Vladimir Cotfas <unix_router@yahoo.com> -- Broadcom Xstrata support
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <asm/byteorder.h>
+#include <linux/if_bridge.h>
+
+#include "log.h"
+#include "mstp.h"
+
+static const char* port_states_bcm[] =
+{
+    [BR_STATE_DISABLED]   = "Disable",
+    [BR_STATE_LISTENING]  = "LIsten",
+    [BR_STATE_LEARNING]   = "LEarn",
+    [BR_STATE_FORWARDING] = "Forward",
+    [BR_STATE_BLOCKING]   = "Disable",
+};
+
+static char* if_linux2bcm(const char* name)
+{
+    if((NULL == name) || ('\0' == name[0]))
+        return "???";
+
+    if(!strcmp(name, "GbE1"))
+        return "ge3";
+    if(!strcmp(name, "GbE2"))
+        return "ge4";
+
+    return "???";
+}
+
+/*
+ * Set new state (BR_STATE_xxx) for the given port and MSTI.
+ * Return new actual state (BR_STATE_xxx) from driver.
+ */
+int driver_set_new_state(per_tree_port_t *ptp, int new_state)
+{
+    unsigned int port;
+    port_t *ifc = ptp->port;
+
+    char cmd[257] = {0};
+    snprintf(cmd, 256, "bcmexp port %s stp=%s &>/dev/null",
+             if_linux2bcm(ifc->sysdeps.name), port_states_bcm[new_state]);
+    Dprintf(2, "CMD: %s\n", cmd);
+    system(cmd);
+
+    return new_state;
+}
+
+bool driver_create_msti(bridge_t *br, __u16 mstid)
+{
+    /* TODO: send "create msti" command to driver */
+    return true;
+}
+
+bool driver_delete_msti(bridge_t *br, __u16 mstid)
+{
+    /* TODO: send "delete msti" command to driver */
+    return true;
+}
+
+/*
+ * Flush L2 forwarding table for a given port
+ */
+void driver_flush_all_fids(per_tree_port_t *ptp)
+{
+    unsigned int port;
+    port_t *ifc = ptp->port;
+
+    char cmd[257] = {0};
+    snprintf(cmd, 256, "bcmexp l2 clear port=%s &>/dev/null",
+             if_linux2bcm(ifc->sysdeps.name));
+    Dprintf(2, "CMD: %s\n", cmd);
+    system(cmd);
+
+    /*
+     * TODO: Confirm that L2 forwarding table is _really_ flushed
+     *   when bcmexp returns. If that is not true and after return from
+     *   bcmexp the flushing is still in process, then we should wait for the
+     *   end of that process (preferably in asynchronous manner) and only than
+     *   call MSTP_IN_all_fids_flushed().
+     */
+    MSTP_IN_all_fids_flushed(ptp);
+}
+
+/*
+ * Set new ageing time (in seconds) for the bridge.
+ * Return new actual ageing time from driver (the ageing timer granularity
+ *  in the hardware can be more than 1 sec)
+ */
+unsigned int driver_set_ageing_time(bridge_t *br, unsigned int ageingTime)
+{
+    /* TODO: do set new ageing time */
+    return ageingTime;
+}
diff --git a/broadcom_xstrata/etc-init.d-stp.sh b/broadcom_xstrata/etc-init.d-stp.sh
new file mode 100644 (file)
index 0000000..aa367f6
--- /dev/null
@@ -0,0 +1,97 @@
+#!/bin/sh
+#
+# Copyright (C) 2012 by Phybridge Inc
+#
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+# source our service control routines
+[ -r /etc/polre/svc-ctl ] && . /etc/polre/svc-ctl
+# spanning tree service
+SVC=stp
+
+MODPATH=/sbin
+DAEMON=mstpd
+DAEMONARGS=
+
+# bridge ID
+BRID=br0
+# lowest
+BRPRIO=0
+
+GBPORTS="GbE1 GbE2"
+GBCOST=4
+
+start() {
+    [ "$VERBOSE" != no ] ||  echo -n "Starting Spanning Tree: "
+    bcmexp rcload /root/stp.soc
+    # launch userland mstpd daemon
+    [ -f $MODPATH/$DAEMON ] || exit 1
+    start-stop-daemon -S -q --exec $MODPATH/$DAEMON -- $DAEMONARGS
+    # create the bridge, add the ports, adjust the costs
+    brctl addbr $BRID  || (echo "nope, already running"; exit 0)
+    brctl setbridgeprio $BRID $BRPRIO
+    brctl addif $BRID $GBPORTS
+    for port in $GBPORTS; do brctl setpathcost $BRID $port $GBCOST; done
+    
+    # turn it on & bring interface up
+    brctl stp $BRID on
+    ifconfig $BRID up
+
+    /sbin/mstpctl setforcevers br0 stp
+    
+    [ "$VERBOSE" != no ] && echo "OK"
+}
+
+stop() {
+    # undo what start did
+    mstpctl delbridge br0
+    ifconfig $BRID down
+    brctl stp $BRID off
+    brctl delif $BRID $STKPORTS $GBPORTS
+    brctl delbr $BRID
+    start-stop-daemon -K -q -p /var/run/$DAEMON.pid
+}
+
+restart() {
+    stop
+    sleep 1
+    start
+}
+
+status() {
+       brctl show
+       brctl showstp $BRID
+       brctl showmacs $BRID
+}
+
+case "$1" in
+  start)
+   if enabled
+   then
+               start
+       else
+               [ "$VERBOSE" != no ] && echo "$SVC disabled" && exit 0
+       fi
+       ;;
+  stop)
+       stop
+       ;;
+  restart|reload)
+       restart
+       ;;
+  enable)
+       enable
+       ;;
+  status)
+       status
+       ;;
+  disable)
+       disable
+    stop
+       ;;
+       *)
+       echo $"Usage: $0 {start|stop|restart|enable|disable|status}"
+       exit 1
+esac
+
+exit $?
+
diff --git a/broadcom_xstrata/knet.soc b/broadcom_xstrata/knet.soc
new file mode 100644 (file)
index 0000000..0947322
--- /dev/null
@@ -0,0 +1,20 @@
+# Copyright (C) 2012 by Phybridge Inc
+# Released under GPLv2
+
+# ...
+
+knet netif create RCPU=no ifname=GbE1 p=4
+knet filter create  desttype=netif destid=4 striptag=1 ingport=4 description=GbE1 priority=10
+knet netif create RCPU=no ifname=GbE2 p=5
+knet filter create  desttype=netif destid=5 striptag=1 ingport=5 description=GbE2 priority=10
+
+# ...
+
+knet filter create desttype=netif destid=54 striptag=1 ingport=4 description=ge4e0 priority=5
+knet filter create desttype=netif destid=54 striptag=1 ingport=5 description=ge5e0 priority=5
+knet filter create desttype=netif destid=54 striptag=1 reason=protocol description=protocola priority=5
+
+# ...
+
+pw report -raw
+pw start
diff --git a/broadcom_xstrata/stp.soc b/broadcom_xstrata/stp.soc
new file mode 100644 (file)
index 0000000..2e49d26
--- /dev/null
@@ -0,0 +1,23 @@
+# Copyright (C) 2012 by Phybridge Inc
+# Released under GPLv2
+
+port ge3 stp=block
+port ge4 stp=block
+
+fp qset clear
+fp qset add dstmac
+fp qset add ethertype
+fp qset add inports
+#fp qset add outports
+fp qset add drop
+
+fp group create 4 777 1
+
+# This is STP and LLDP as LLDP uses the same mcast MAC as STP
+fp entry create 777 512
+fp qual 512 dstmac data=01:80:c2:00:00:00 mask=ff:ff:ff:00:00:00
+fp action add 512 copytocpu 1 31
+fp entry install 512
+
+knetctrl filter create DestType=NetIF DestID=4 StripTag=yes IngPort=ge3 FPRule=31 DESCription="ge3-fp"
+knetctrl filter create DestType=NetIF DestID=5 StripTag=yes IngPort=ge4 FPRule=31 DESCription="ge4-fp"