usb: cdc_ether: Add new product id for the 5AE profile
[linux-2.6.git] / drivers / net / sunvnet.c
index d764e4c..bf3c762 100644 (file)
@@ -1,8 +1,10 @@
 /* sunvnet.c: Sun LDOM Virtual Network Driver.
  *
- * Copyright (C) 2007 David S. Miller <davem@davemloft.net>
+ * Copyright (C) 2007, 2008 David S. Miller <davem@davemloft.net>
  */
 
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/types.h>
@@ -12,6 +14,7 @@
 #include <linux/netdevice.h>
 #include <linux/ethtool.h>
 #include <linux/etherdevice.h>
+#include <linux/mutex.h>
 
 #include <asm/vio.h>
 #include <asm/ldc.h>
@@ -19,7 +22,6 @@
 #include "sunvnet.h"
 
 #define DRV_MODULE_NAME                "sunvnet"
-#define PFX DRV_MODULE_NAME    ": "
 #define DRV_MODULE_VERSION     "1.0"
 #define DRV_MODULE_RELDATE     "June 25, 2007"
 
@@ -44,9 +46,9 @@ static int vnet_handle_unknown(struct vnet_port *port, void *arg)
 {
        struct vio_msg_tag *pkt = arg;
 
-       printk(KERN_ERR PFX "Received unknown msg [%02x:%02x:%04x:%08x]\n",
+       pr_err("Received unknown msg [%02x:%02x:%04x:%08x]\n",
               pkt->type, pkt->stype, pkt->stype_env, pkt->sid);
-       printk(KERN_ERR PFX "Resetting connection.\n");
+       pr_err("Resetting connection\n");
 
        ldc_disconnect(port->vio.lp);
 
@@ -335,7 +337,7 @@ static int vnet_walk_rx_one(struct vnet_port *port,
        if (IS_ERR(desc))
                return PTR_ERR(desc);
 
-       viodbg(DATA, "vio_walk_rx_one desc[%02x:%02x:%08x:%08x:%lx:%lx]\n",
+       viodbg(DATA, "vio_walk_rx_one desc[%02x:%02x:%08x:%08x:%llx:%llx]\n",
               desc->hdr.state, desc->hdr.ack,
               desc->size, desc->ncookies,
               desc->cookies[0].cookie_addr,
@@ -393,14 +395,14 @@ static int vnet_rx(struct vnet_port *port, void *msgbuf)
        struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_RX_RING];
        struct vio_driver_state *vio = &port->vio;
 
-       viodbg(DATA, "vnet_rx stype_env[%04x] seq[%016lx] rcv_nxt[%016lx]\n",
+       viodbg(DATA, "vnet_rx stype_env[%04x] seq[%016llx] rcv_nxt[%016llx]\n",
               pkt->tag.stype_env, pkt->seq, dr->rcv_nxt);
 
        if (unlikely(pkt->tag.stype_env != VIO_DRING_DATA))
                return 0;
        if (unlikely(pkt->seq != dr->rcv_nxt)) {
-               printk(KERN_ERR PFX "RX out of sequence seq[0x%lx] "
-                      "rcv_nxt[0x%lx]\n", pkt->seq, dr->rcv_nxt);
+               pr_err("RX out of sequence seq[0x%llx] rcv_nxt[0x%llx]\n",
+                      pkt->seq, dr->rcv_nxt);
                return 0;
        }
 
@@ -458,6 +460,21 @@ 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)
+               pr_err("%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;
@@ -497,11 +514,13 @@ static void vnet_event(void *arg, int event)
                vio_link_state_change(vio, event);
                spin_unlock_irqrestore(&vio->lock, flags);
 
+               if (event == LDC_EVENT_RESET)
+                       vio_port_up(vio);
                return;
        }
 
        if (unlikely(event != LDC_EVENT_DATA_READY)) {
-               printk(KERN_WARNING PFX "Unexpected LDC event %d\n", event);
+               pr_warning("Unexpected LDC event %d\n", event);
                spin_unlock_irqrestore(&vio->lock, flags);
                return;
        }
@@ -541,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 {
@@ -640,8 +662,7 @@ static int vnet_start_xmit(struct sk_buff *skb, struct net_device *dev)
                        netif_stop_queue(dev);
 
                        /* This is a hard error, log it. */
-                       printk(KERN_ERR PFX "%s: BUG! Tx Ring full when "
-                              "queue awake!\n", dev->name);
+                       netdev_err(dev, "BUG! Tx Ring full when queue awake!\n");
                        dev->stats.tx_errors++;
                }
                spin_unlock_irqrestore(&port->vio.lock, flags);
@@ -674,8 +695,7 @@ static int vnet_start_xmit(struct sk_buff *skb, struct net_device *dev)
 
        err = __vnet_tx_trigger(port);
        if (unlikely(err < 0)) {
-               printk(KERN_INFO PFX "%s: TX trigger error %d\n",
-                      dev->name, err);
+               netdev_info(dev, "TX trigger error %d\n", err);
                d->hdr.state = VIO_DESC_FREE;
                dev->stats.tx_carrier_errors++;
                goto out_dropped_unlock;
@@ -695,7 +715,6 @@ static int vnet_start_xmit(struct sk_buff *skb, struct net_device *dev)
 
        dev_kfree_skb(skb);
 
-       dev->trans_start = jiffies;
        return NETDEV_TX_OK;
 
 out_dropped_unlock:
@@ -728,9 +747,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 netdev_hw_addr *ha;
+
+       netdev_for_each_mc_addr(ha, dev) {
+               struct vnet_mcast_entry *m;
+
+               m = __vnet_mc_find(vp, ha->addr);
+               if (m) {
+                       m->hit = 1;
+                       continue;
+               }
+
+               if (!m) {
+                       m = kzalloc(sizeof(*m), GFP_ATOMIC);
+                       if (!m)
+                               continue;
+                       memcpy(m->addr, ha->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)
@@ -771,7 +903,6 @@ static const struct ethtool_ops vnet_ethtool_ops = {
        .get_msglevel           = vnet_get_msglevel,
        .set_msglevel           = vnet_set_msglevel,
        .get_link               = ethtool_op_get_link,
-       .get_perm_addr          = ethtool_op_get_perm_addr,
 };
 
 static void vnet_port_free_tx_bufs(struct vnet_port *port)
@@ -819,12 +950,12 @@ static int __devinit vnet_port_alloc_tx_bufs(struct vnet_port *port)
 
                err = -ENOMEM;
                if (!buf) {
-                       printk(KERN_ERR "TX buffer allocation failure\n");
+                       pr_err("TX buffer allocation failure\n");
                        goto err_out;
                }
                err = -EFAULT;
                if ((unsigned long)buf & (8UL - 1)) {
-                       printk(KERN_ERR "TX buffer misaligned\n");
+                       pr_err("TX buffer misaligned\n");
                        kfree(buf);
                        goto err_out;
                }
@@ -875,6 +1006,116 @@ err_out:
        return err;
 }
 
+static LIST_HEAD(vnet_list);
+static DEFINE_MUTEX(vnet_list_mutex);
+
+static const struct net_device_ops vnet_ops = {
+       .ndo_open               = vnet_open,
+       .ndo_stop               = vnet_close,
+       .ndo_set_multicast_list = vnet_set_rx_mode,
+       .ndo_set_mac_address    = vnet_set_mac_addr,
+       .ndo_validate_addr      = eth_validate_addr,
+       .ndo_tx_timeout         = vnet_tx_timeout,
+       .ndo_change_mtu         = vnet_change_mtu,
+       .ndo_start_xmit         = vnet_start_xmit,
+};
+
+static struct vnet * __devinit vnet_new(const u64 *local_mac)
+{
+       struct net_device *dev;
+       struct vnet *vp;
+       int err, i;
+
+       dev = alloc_etherdev(sizeof(*vp));
+       if (!dev) {
+               pr_err("Etherdev alloc failed, aborting\n");
+               return ERR_PTR(-ENOMEM);
+       }
+
+       for (i = 0; i < ETH_ALEN; i++)
+               dev->dev_addr[i] = (*local_mac >> (5 - i) * 8) & 0xff;
+
+       memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len);
+
+       vp = netdev_priv(dev);
+
+       spin_lock_init(&vp->lock);
+       vp->dev = dev;
+
+       INIT_LIST_HEAD(&vp->port_list);
+       for (i = 0; i < VNET_PORT_HASH_SIZE; i++)
+               INIT_HLIST_HEAD(&vp->port_hash[i]);
+       INIT_LIST_HEAD(&vp->list);
+       vp->local_mac = *local_mac;
+
+       dev->netdev_ops = &vnet_ops;
+       dev->ethtool_ops = &vnet_ethtool_ops;
+       dev->watchdog_timeo = VNET_TX_TIMEOUT;
+
+       err = register_netdev(dev);
+       if (err) {
+               pr_err("Cannot register net device, aborting\n");
+               goto err_out_free_dev;
+       }
+
+       netdev_info(dev, "Sun LDOM vnet %pM\n", dev->dev_addr);
+
+       list_add(&vp->list, &vnet_list);
+
+       return vp;
+
+err_out_free_dev:
+       free_netdev(dev);
+
+       return ERR_PTR(err);
+}
+
+static struct vnet * __devinit vnet_find_or_create(const u64 *local_mac)
+{
+       struct vnet *iter, *vp;
+
+       mutex_lock(&vnet_list_mutex);
+       vp = NULL;
+       list_for_each_entry(iter, &vnet_list, list) {
+               if (iter->local_mac == *local_mac) {
+                       vp = iter;
+                       break;
+               }
+       }
+       if (!vp)
+               vp = vnet_new(local_mac);
+       mutex_unlock(&vnet_list_mutex);
+
+       return vp;
+}
+
+static const char *local_mac_prop = "local-mac-address";
+
+static struct vnet * __devinit vnet_find_parent(struct mdesc_handle *hp,
+                                               u64 port_node)
+{
+       const u64 *local_mac = NULL;
+       u64 a;
+
+       mdesc_for_each_arc(a, hp, port_node, MDESC_ARC_TYPE_BACK) {
+               u64 target = mdesc_arc_target(hp, a);
+               const char *name;
+
+               name = mdesc_get_property(hp, target, "name", NULL);
+               if (!name || strcmp(name, "network"))
+                       continue;
+
+               local_mac = mdesc_get_property(hp, target,
+                                              local_mac_prop, NULL);
+               if (local_mac)
+                       break;
+       }
+       if (!local_mac)
+               return ERR_PTR(-ENODEV);
+
+       return vnet_find_or_create(local_mac);
+}
+
 static struct ldc_channel_config vnet_ldc_cfg = {
        .event          = vnet_event,
        .mtu            = 64,
@@ -887,41 +1128,46 @@ static struct vio_driver_ops vnet_vio_ops = {
        .handshake_complete     = vnet_handshake_complete,
 };
 
+static void __devinit print_version(void)
+{
+       printk_once(KERN_INFO "%s", version);
+}
+
 const char *remote_macaddr_prop = "remote-mac-address";
 
 static int __devinit vnet_port_probe(struct vio_dev *vdev,
                                     const struct vio_device_id *id)
 {
-       struct mdesc_node *endp;
+       struct mdesc_handle *hp;
        struct vnet_port *port;
        unsigned long flags;
        struct vnet *vp;
        const u64 *rmac;
        int len, i, err, switch_port;
 
-       vp = dev_get_drvdata(vdev->dev.parent);
-       if (!vp) {
-               printk(KERN_ERR PFX "Cannot find port parent vnet.\n");
-               return -ENODEV;
-       }
+       print_version();
 
-       rmac = md_get_property(vdev->mp, remote_macaddr_prop, &len);
-       if (!rmac) {
-               printk(KERN_ERR PFX "Port lacks %s property.\n",
-                      remote_macaddr_prop);
-               return -ENODEV;
+       hp = mdesc_grab();
+
+       vp = vnet_find_parent(hp, vdev->mp);
+       if (IS_ERR(vp)) {
+               pr_err("Cannot find port parent vnet\n");
+               err = PTR_ERR(vp);
+               goto err_out_put_mdesc;
        }
 
-       endp = vio_find_endpoint(vdev);
-       if (!endp) {
-               printk(KERN_ERR PFX "Port lacks channel-endpoint.\n");
-               return -ENODEV;
+       rmac = mdesc_get_property(hp, vdev->mp, remote_macaddr_prop, &len);
+       err = -ENODEV;
+       if (!rmac) {
+               pr_err("Port lacks %s property\n", remote_macaddr_prop);
+               goto err_out_put_mdesc;
        }
 
        port = kzalloc(sizeof(*port), GFP_KERNEL);
+       err = -ENOMEM;
        if (!port) {
-               printk(KERN_ERR PFX "Cannot allocate vnet_port.\n");
-               return -ENOMEM;
+               pr_err("Cannot allocate vnet_port\n");
+               goto err_out_put_mdesc;
        }
 
        for (i = 0; i < ETH_ALEN; i++)
@@ -929,7 +1175,7 @@ static int __devinit vnet_port_probe(struct vio_dev *vdev,
 
        port->vp = vp;
 
-       err = vio_driver_init(&port->vio, vdev, VDEV_NETWORK, endp,
+       err = vio_driver_init(&port->vio, vdev, VDEV_NETWORK,
                              vnet_versions, ARRAY_SIZE(vnet_versions),
                              &vnet_vio_ops, vp->dev->name);
        if (err)
@@ -947,8 +1193,9 @@ static int __devinit vnet_port_probe(struct vio_dev *vdev,
        INIT_LIST_HEAD(&port->list);
 
        switch_port = 0;
-       if (md_get_property(vdev->mp, "switch-port", NULL) != NULL)
+       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)
@@ -960,15 +1207,13 @@ static int __devinit vnet_port_probe(struct vio_dev *vdev,
 
        dev_set_drvdata(&vdev->dev, port);
 
-       printk(KERN_INFO "%s: PORT ( remote-mac ", vp->dev->name);
-       for (i = 0; i < 6; i++)
-               printk("%2.2x%c", port->raddr[i], i == 5 ? ' ' : ':');
-       if (switch_port)
-               printk("switch-port ");
-       printk(")\n");
+       pr_info("%s: PORT ( remote-mac %pM%s )\n",
+               vp->dev->name, port->raddr, switch_port ? " switch-port" : "");
 
        vio_port_up(&port->vio);
 
+       mdesc_release(hp);
+
        return 0;
 
 err_out_free_ldc:
@@ -977,6 +1222,8 @@ err_out_free_ldc:
 err_out_free_port:
        kfree(port);
 
+err_out_put_mdesc:
+       mdesc_release(hp);
        return err;
 }
 
@@ -1005,13 +1252,13 @@ static int vnet_port_remove(struct vio_dev *vdev)
        return 0;
 }
 
-static struct vio_device_id vnet_port_match[] = {
+static const struct vio_device_id vnet_port_match[] = {
        {
                .type = "vnet-port",
        },
        {},
 };
-MODULE_DEVICE_TABLE(vio, vnet_match);
+MODULE_DEVICE_TABLE(vio, vnet_port_match);
 
 static struct vio_driver vnet_port_driver = {
        .id_table       = vnet_port_match,
@@ -1023,133 +1270,14 @@ static struct vio_driver vnet_port_driver = {
        }
 };
 
-const char *local_mac_prop = "local-mac-address";
-
-static int __devinit vnet_probe(struct vio_dev *vdev,
-                               const struct vio_device_id *id)
-{
-       static int vnet_version_printed;
-       struct net_device *dev;
-       struct vnet *vp;
-       const u64 *mac;
-       int err, i, len;
-
-       if (vnet_version_printed++ == 0)
-               printk(KERN_INFO "%s", version);
-
-       mac = md_get_property(vdev->mp, local_mac_prop, &len);
-       if (!mac) {
-               printk(KERN_ERR PFX "vnet lacks %s property.\n",
-                      local_mac_prop);
-               err = -ENODEV;
-               goto err_out;
-       }
-
-       dev = alloc_etherdev(sizeof(*vp));
-       if (!dev) {
-               printk(KERN_ERR PFX "Etherdev alloc failed, aborting.\n");
-               err = -ENOMEM;
-               goto err_out;
-       }
-
-       for (i = 0; i < ETH_ALEN; i++)
-               dev->dev_addr[i] = (*mac >> (5 - i) * 8) & 0xff;
-
-       memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len);
-
-       SET_NETDEV_DEV(dev, &vdev->dev);
-
-       vp = netdev_priv(dev);
-
-       spin_lock_init(&vp->lock);
-       vp->dev = dev;
-       vp->vdev = vdev;
-
-       INIT_LIST_HEAD(&vp->port_list);
-       for (i = 0; i < VNET_PORT_HASH_SIZE; i++)
-               INIT_HLIST_HEAD(&vp->port_hash[i]);
-
-       dev->open = vnet_open;
-       dev->stop = vnet_close;
-       dev->set_multicast_list = vnet_set_rx_mode;
-       dev->set_mac_address = vnet_set_mac_addr;
-       dev->tx_timeout = vnet_tx_timeout;
-       dev->ethtool_ops = &vnet_ethtool_ops;
-       dev->watchdog_timeo = VNET_TX_TIMEOUT;
-       dev->change_mtu = vnet_change_mtu;
-       dev->hard_start_xmit = vnet_start_xmit;
-
-       err = register_netdev(dev);
-       if (err) {
-               printk(KERN_ERR PFX "Cannot register net device, "
-                      "aborting.\n");
-               goto err_out_free_dev;
-       }
-
-       printk(KERN_INFO "%s: Sun LDOM vnet ", dev->name);
-
-       for (i = 0; i < 6; i++)
-               printk("%2.2x%c", dev->dev_addr[i], i == 5 ? '\n' : ':');
-
-       dev_set_drvdata(&vdev->dev, vp);
-
-       return 0;
-
-err_out_free_dev:
-       free_netdev(dev);
-
-err_out:
-       return err;
-}
-
-static int vnet_remove(struct vio_dev *vdev)
-{
-
-       struct vnet *vp = dev_get_drvdata(&vdev->dev);
-
-       if (vp) {
-               /* XXX unregister port, or at least check XXX */
-               unregister_netdevice(vp->dev);
-               dev_set_drvdata(&vdev->dev, NULL);
-       }
-       return 0;
-}
-
-static struct vio_device_id vnet_match[] = {
-       {
-               .type = "network",
-       },
-       {},
-};
-MODULE_DEVICE_TABLE(vio, vnet_match);
-
-static struct vio_driver vnet_driver = {
-       .id_table       = vnet_match,
-       .probe          = vnet_probe,
-       .remove         = vnet_remove,
-       .driver         = {
-               .name   = "vnet",
-               .owner  = THIS_MODULE,
-       }
-};
-
 static int __init vnet_init(void)
 {
-       int err = vio_register_driver(&vnet_driver);
-
-       if (!err) {
-               err = vio_register_driver(&vnet_port_driver);
-               if (err)
-                       vio_unregister_driver(&vnet_driver);
-       }
-
-       return err;
+       return vio_register_driver(&vnet_port_driver);
 }
 
 static void __exit vnet_exit(void)
 {
        vio_unregister_driver(&vnet_port_driver);
-       vio_unregister_driver(&vnet_driver);
 }
 
 module_init(vnet_init);