]> pilppa.com Git - linux-2.6-omap-h63xx.git/commitdiff
[SPARC64]: Add proper multicast support to VNET driver.
authorDavid S. Miller <davem@sunset.davemloft.net>
Fri, 20 Jul 2007 09:30:25 +0000 (02:30 -0700)
committerDavid S. Miller <davem@sunset.davemloft.net>
Sat, 21 Jul 2007 00:14:30 +0000 (17:14 -0700)
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/sunvnet.c
drivers/net/sunvnet.h

index ef0066bab2cf77af11f99a2306e51a9a521402ca..61f98251feab04f428e4769c112ac8c4059e33be 100644 (file)
@@ -459,6 +459,22 @@ static int vnet_nack(struct vnet_port *port, void *msgbuf)
        return 0;
 }
 
+static int handle_mcast(struct vnet_port *port, void *msgbuf)
+{
+       struct vio_net_mcast_info *pkt = msgbuf;
+
+       if (pkt->tag.stype != VIO_SUBTYPE_ACK)
+               printk(KERN_ERR PFX "%s: Got unexpected MCAST reply "
+                      "[%02x:%02x:%04x:%08x]\n",
+                      port->vp->dev->name,
+                      pkt->tag.type,
+                      pkt->tag.stype,
+                      pkt->tag.stype_env,
+                      pkt->tag.sid);
+
+       return 0;
+}
+
 static void maybe_tx_wakeup(struct vnet *vp)
 {
        struct net_device *dev = vp->dev;
@@ -544,7 +560,10 @@ static void vnet_event(void *arg, int event)
                                err = vnet_nack(port, &msgbuf);
                        }
                } else if (msgbuf.tag.type == VIO_TYPE_CTRL) {
-                       err = vio_control_pkt_engine(vio, &msgbuf);
+                       if (msgbuf.tag.stype_env == VNET_MCAST_INFO)
+                               err = handle_mcast(port, &msgbuf);
+                       else
+                               err = vio_control_pkt_engine(vio, &msgbuf);
                        if (err)
                                break;
                } else {
@@ -731,9 +750,122 @@ static int vnet_close(struct net_device *dev)
        return 0;
 }
 
+static struct vnet_mcast_entry *__vnet_mc_find(struct vnet *vp, u8 *addr)
+{
+       struct vnet_mcast_entry *m;
+
+       for (m = vp->mcast_list; m; m = m->next) {
+               if (!memcmp(m->addr, addr, ETH_ALEN))
+                       return m;
+       }
+       return NULL;
+}
+
+static void __update_mc_list(struct vnet *vp, struct net_device *dev)
+{
+       struct dev_addr_list *p;
+
+       for (p = dev->mc_list; p; p = p->next) {
+               struct vnet_mcast_entry *m;
+
+               m = __vnet_mc_find(vp, p->dmi_addr);
+               if (m) {
+                       m->hit = 1;
+                       continue;
+               }
+
+               if (!m) {
+                       m = kzalloc(sizeof(*m), GFP_ATOMIC);
+                       if (!m)
+                               continue;
+                       memcpy(m->addr, p->dmi_addr, ETH_ALEN);
+                       m->hit = 1;
+
+                       m->next = vp->mcast_list;
+                       vp->mcast_list = m;
+               }
+       }
+}
+
+static void __send_mc_list(struct vnet *vp, struct vnet_port *port)
+{
+       struct vio_net_mcast_info info;
+       struct vnet_mcast_entry *m, **pp;
+       int n_addrs;
+
+       memset(&info, 0, sizeof(info));
+
+       info.tag.type = VIO_TYPE_CTRL;
+       info.tag.stype = VIO_SUBTYPE_INFO;
+       info.tag.stype_env = VNET_MCAST_INFO;
+       info.tag.sid = vio_send_sid(&port->vio);
+       info.set = 1;
+
+       n_addrs = 0;
+       for (m = vp->mcast_list; m; m = m->next) {
+               if (m->sent)
+                       continue;
+               m->sent = 1;
+               memcpy(&info.mcast_addr[n_addrs * ETH_ALEN],
+                      m->addr, ETH_ALEN);
+               if (++n_addrs == VNET_NUM_MCAST) {
+                       info.count = n_addrs;
+
+                       (void) vio_ldc_send(&port->vio, &info,
+                                           sizeof(info));
+                       n_addrs = 0;
+               }
+       }
+       if (n_addrs) {
+               info.count = n_addrs;
+               (void) vio_ldc_send(&port->vio, &info, sizeof(info));
+       }
+
+       info.set = 0;
+
+       n_addrs = 0;
+       pp = &vp->mcast_list;
+       while ((m = *pp) != NULL) {
+               if (m->hit) {
+                       m->hit = 0;
+                       pp = &m->next;
+                       continue;
+               }
+
+               memcpy(&info.mcast_addr[n_addrs * ETH_ALEN],
+                      m->addr, ETH_ALEN);
+               if (++n_addrs == VNET_NUM_MCAST) {
+                       info.count = n_addrs;
+                       (void) vio_ldc_send(&port->vio, &info,
+                                           sizeof(info));
+                       n_addrs = 0;
+               }
+
+               *pp = m->next;
+               kfree(m);
+       }
+       if (n_addrs) {
+               info.count = n_addrs;
+               (void) vio_ldc_send(&port->vio, &info, sizeof(info));
+       }
+}
+
 static void vnet_set_rx_mode(struct net_device *dev)
 {
-       /* XXX Implement multicast support XXX */
+       struct vnet *vp = netdev_priv(dev);
+       struct vnet_port *port;
+       unsigned long flags;
+
+       spin_lock_irqsave(&vp->lock, flags);
+       if (!list_empty(&vp->port_list)) {
+               port = list_entry(vp->port_list.next, struct vnet_port, list);
+
+               if (port->switch_port) {
+                       __update_mc_list(vp, dev);
+                       __send_mc_list(vp, port);
+               }
+       }
+       spin_unlock_irqrestore(&vp->lock, flags);
 }
 
 static int vnet_change_mtu(struct net_device *dev, int new_mtu)
@@ -1070,6 +1202,7 @@ static int __devinit vnet_port_probe(struct vio_dev *vdev,
        switch_port = 0;
        if (mdesc_get_property(hp, vdev->mp, "switch-port", NULL) != NULL)
                switch_port = 1;
+       port->switch_port = switch_port;
 
        spin_lock_irqsave(&vp->lock, flags);
        if (switch_port)
index 7d3a0cac727b5a06bdda3dd4932c76df01760c85..d347a5bf24b00d1f69e50dc78106086ae3e99eac 100644 (file)
@@ -30,6 +30,8 @@ struct vnet_port {
 
        struct hlist_node       hash;
        u8                      raddr[ETH_ALEN];
+       u8                      switch_port;
+       u8                      __pad;
 
        struct vnet             *vp;
 
@@ -53,6 +55,13 @@ static inline unsigned int vnet_hashfn(u8 *mac)
        return val & (VNET_PORT_HASH_MASK);
 }
 
+struct vnet_mcast_entry {
+       u8                      addr[ETH_ALEN];
+       u8                      sent;
+       u8                      hit;
+       struct vnet_mcast_entry *next;
+};
+
 struct vnet {
        /* Protects port_list and port_hash.  */
        spinlock_t              lock;
@@ -65,6 +74,8 @@ struct vnet {
 
        struct hlist_head       port_hash[VNET_PORT_HASH_SIZE];
 
+       struct vnet_mcast_entry *mcast_list;
+
        struct list_head        list;
        u64                     local_mac;
 };