From: dv1tas Date: Wed, 1 Aug 2012 08:37:42 +0000 (+0000) Subject: Broadcom Xstrata support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=85ccf48bae309d59f4bb00518a3975992c133b81;p=people%2Fms%2Fmstpd.git Broadcom Xstrata support Signed-off-by: Vladimir Cotfas git-svn-id: svn://svn.code.sf.net/p/mstpd/code/trunk@34 fbe50366-0c72-4402-a84b-5d246361dba7 --- diff --git a/broadcom_xstrata/README b/broadcom_xstrata/README new file mode 100644 index 0000000..2b51d20 --- /dev/null +++ b/broadcom_xstrata/README @@ -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 + +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 index 0000000..07b57ab --- /dev/null +++ b/broadcom_xstrata/bcmexp.c @@ -0,0 +1,467 @@ +/* + * Based on Bill Evans's getmale.c + * + * Copyright (C) 2012 by Vladimir Cotfas + * + * Released under GPLv2 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..e72e9e3 --- /dev/null +++ b/broadcom_xstrata/driver_deps.c @@ -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 + * Authors: Vladimir Cotfas -- Broadcom Xstrata support + */ + +#include +#include +#include +#include +#include + +#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 index 0000000..aa367f6 --- /dev/null +++ b/broadcom_xstrata/etc-init.d-stp.sh @@ -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 index 0000000..0947322 --- /dev/null +++ b/broadcom_xstrata/knet.soc @@ -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 index 0000000..2e49d26 --- /dev/null +++ b/broadcom_xstrata/stp.soc @@ -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"