From 4488b8853040a901425ceb390f71e3a1942066e1 Mon Sep 17 00:00:00 2001 From: Till Straumann Date: Sat, 6 Jun 2009 00:45:57 +0000 Subject: 2009-06-05 Till Straumann * network/tsec.c, network/if_tsec_pub.h: added multicast support. --- c/src/lib/libbsp/powerpc/mvme3100/ChangeLog | 4 + .../libbsp/powerpc/mvme3100/network/if_tsec_pub.h | 34 +++++++ c/src/lib/libbsp/powerpc/mvme3100/network/tsec.c | 106 ++++++++++++++++++--- 3 files changed, 133 insertions(+), 11 deletions(-) (limited to 'c/src') diff --git a/c/src/lib/libbsp/powerpc/mvme3100/ChangeLog b/c/src/lib/libbsp/powerpc/mvme3100/ChangeLog index 78ad7499ff..bba0bf74ea 100644 --- a/c/src/lib/libbsp/powerpc/mvme3100/ChangeLog +++ b/c/src/lib/libbsp/powerpc/mvme3100/ChangeLog @@ -1,3 +1,7 @@ +2009-06-05 Till Straumann + * network/tsec.c, network/if_tsec_pub.h: + added multicast support. + 2009-03-05 Till Straumann * startup/bspstart.c: removed legacy code (inherited diff --git a/c/src/lib/libbsp/powerpc/mvme3100/network/if_tsec_pub.h b/c/src/lib/libbsp/powerpc/mvme3100/network/if_tsec_pub.h index da3f2bc1a7..0a7760d0c1 100644 --- a/c/src/lib/libbsp/powerpc/mvme3100/network/if_tsec_pub.h +++ b/c/src/lib/libbsp/powerpc/mvme3100/network/if_tsec_pub.h @@ -173,10 +173,44 @@ BSP_tsec_reset_stats(struct tsec_private *mp); * 'promisc' whether to set promiscuous flag. * 'enaddr' pointer to six bytes with MAC address. Read * from the device if NULL. + * NOTE: multicast filter is cleared by this routine. */ void BSP_tsec_init_hw(struct tsec_private *mp, int promisc, unsigned char *enaddr); +/* + * Clear multicast hash filter. No multicast frames are accepted + * after executing this routine (unless the hardware was initialized + * in 'promiscuous' mode). + */ +void +BSP_tsec_mcast_filter_clear(struct tsec_private *mp); + +/* + * Program multicast filter to accept all multicast frames. + */ +void +BSP_tsec_mcast_filter_accept_all(struct tsec_private *mp); + +/* + * Add a MAC address to the multicast filter. + * Existing entries are not changed but note that + * the filter is imperfect, i.e., multiple MAC addresses + * may alias to a single filter entry. Hence software + * filtering must still be performed. + * + * NOTE: Deletion of an address is not possible. This is + * usually accomplished by a higher-level driver + * maintaining a list/database of multicast addresses + * and going through a sequence: + * + * BSP_tsec_mcast_filter_clear() + * forall mcast addresses do + * BSP_tsec_mcast_filter_accept_add() + */ +void +BSP_tsec_mcast_filter_accept_add(struct tsec_private *mp, unsigned char *enaddr); + /* * Dump statistics to FILE 'f'. If NULL, stdout is used. */ diff --git a/c/src/lib/libbsp/powerpc/mvme3100/network/tsec.c b/c/src/lib/libbsp/powerpc/mvme3100/network/tsec.c index 993a4ee306..3127466b4a 100644 --- a/c/src/lib/libbsp/powerpc/mvme3100/network/tsec.c +++ b/c/src/lib/libbsp/powerpc/mvme3100/network/tsec.c @@ -50,6 +50,7 @@ #include #include #include +#include #include #include @@ -113,6 +114,9 @@ phy_irq_pending(struct tsec_private *mp); static uint32_t phy_ack_irq(struct tsec_private *mp); +static void +tsec_update_mcast(struct ifnet *ifp); + #if defined(PARANOIA) || defined(DEBUG) void tsec_dump_tring(struct tsec_private *mp); void tsec_dump_rring(struct tsec_private *mp); @@ -1339,9 +1343,10 @@ rtems_interrupt_level l; for ( i=0; i<8*4; i+=4 ) { fec_wr( b, TSEC_IADDR0 + i, 0 ); - fec_wr( b, TSEC_GADDR0 + i, 0 ); } + BSP_tsec_mcast_filter_clear(mp); + BSP_tsec_reset_stats(mp); fec_wr( b, TSEC_ATTR, (TSEC_ATTR_RDSEN | TSEC_ATTR_RBDSEN) ); @@ -1396,6 +1401,54 @@ rtems_interrupt_level l; rtems_interrupt_enable( l ); } +static uint8_t +hash_accept(struct tsec_private *mp, uint32_t tble, const uint8_t *enaddr) +{ +uint8_t s; + + s = ether_crc32_le(enaddr, ETHER_ADDR_LEN); + + /* bit-reverse */ + s = ((s&0x0f) << 4) | ((s&0xf0) >> 4); + s = ((s&0x33) << 2) | ((s&0xcc) >> 2); + s = ((s&0x55) << 1) | ((s&0xaa) >> 1); + + fec_wr( mp->base, tble + (s >> (5-2)), (1 << (31 - (s & 31))) ); +} + +void +BSP_tsec_mcast_filter_clear(struct tsec_private *mp) +{ +int i; + for ( i=0; i<8*4; i+=4 ) { + fec_wr( mp->base, TSEC_GADDR0 + i, 0 ); + } +} + +void +BSP_tsec_mcast_filter_accept_all(struct tsec_private *mp) +{ +int i; + for ( i=0; i<8*4; i+=4 ) { + fec_wr( mp->base, TSEC_GADDR0 + i, 0xffffffff ); + } +} + +void +BSP_tsec_mcast_filter_accept_add(struct tsec_private *mp, uint8_t *enaddr) +{ +static const uint8_t bcst={0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + if ( ! (enaddr[0] & 0x01) ) { + /* not a multicast address; ignore */ + return; + } + if ( 0 == memcmp( enaddr, bcst, sizeof(bcst) ) ) { + /* broadcast; ignore */ + return; + } + hash_accept(mp, TSEC_GADDR0, enaddr); +} + void BSP_tsec_dump_stats(struct tsec_private *mp, FILE *f) { @@ -2310,6 +2363,8 @@ tsec_init(void *arg) struct tsec_softc *sc = arg; struct ifnet *ifp = &sc->arpcom.ac_if; BSP_tsec_init_hw(&sc->pvt, ifp->if_flags & IFF_PROMISC, sc->arpcom.ac_enaddr); + + tsec_update_mcast(ifp); ifp->if_flags |= IFF_RUNNING; sc->arpcom.ac_if.if_timer = 0; } @@ -2348,6 +2403,31 @@ struct tsec_softc *sc = ifp->if_softc; tsec_start(ifp); } +static void +tsec_update_mcast(struct ifnet *ifp) +{ +struct tsec_softc *sc = ifp->if_softc; +struct ether_multi *enm; +struct ether_multistep step; + + if ( IFF_ALLMULTI & ifp->if_flags ) { + BSP_tsec_mcast_filter_accept_all( &sc->pvt ); + } else { + BSP_tsec_mcast_filter_clear( &sc->pvt ); + + ETHER_FIRST_MULTI(step, (struct arpcom *)ifp, enm); + + while ( enm ) { + if ( memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN) ) + assert( !"Should never get here; IFF_ALLMULTI should be set!" ); + + BSP_tsec_mcast_filter_accept_add(&sc->pvt, enm->enm_addrlo); + + ETHER_NEXT_MULTI(step, enm); + } + } +} + /* bsdnet driver ioctl entry */ static int tsec_ioctl(struct ifnet *ifp, ioctl_command_t cmd, caddr_t data) @@ -2395,15 +2475,19 @@ int f; error = BSP_tsec_media_ioctl(&sc->pvt, cmd, &ifr->ifr_media); break; -/* - * TODO - * - * case SIOCADDMULTI: - * case SIOCDELMULTI: - * - * break; - */ - + case SIOCADDMULTI: + case SIOCDELMULTI: + error = (cmd == SIOCADDMULTI) + ? ether_addmulti(ifr, &sc->arpcom) + : ether_delmulti(ifr, &sc->arpcom); + + if (error == ENETRESET) { + if (ifp->if_flags & IFF_RUNNING) { + tsec_update_mcast(ifp); + } + error = 0; + } + break; case SIO_RTEMS_SHOW_STATS: BSP_tsec_dump_stats( &sc->pvt, stdout ); @@ -2570,7 +2654,7 @@ struct ifnet *ifp; ifp->if_timer = 0; sc->bsd.oif_flags = /* ... */ - ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX; + ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST | IFF_SIMPLEX; /* * if unset, this set to 10Mbps by ether_ifattach; seems to be unused by bsdnet stack; -- cgit v1.2.3