]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/scsi/fcoe/fcoe.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/fs/xfs/xfs
[karo-tx-linux.git] / drivers / scsi / fcoe / fcoe.c
index c9a0346e493a9ee798e4902ff28e634ccd99bd13..704b8e034946f9522b1ccd13c665a560371c0d8b 100644 (file)
@@ -57,9 +57,12 @@ MODULE_PARM_DESC(ddp_min, "Minimum I/O size in bytes for "   \
 
 DEFINE_MUTEX(fcoe_config_mutex);
 
+/* fcoe_percpu_clean completion.  Waiter protected by fcoe_create_mutex */
+static DECLARE_COMPLETION(fcoe_flush_completion);
+
 /* fcoe host list */
+/* must only by accessed under the RTNL mutex */
 LIST_HEAD(fcoe_hostlist);
-DEFINE_RWLOCK(fcoe_hostlist_lock);
 DEFINE_PER_CPU(struct fcoe_percpu_s, fcoe_percpu);
 
 /* Function Prototypes */
@@ -74,12 +77,13 @@ static int fcoe_link_ok(struct fc_lport *lp);
 
 static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *);
 static int fcoe_hostlist_add(const struct fc_lport *);
-static int fcoe_hostlist_remove(const struct fc_lport *);
 
 static void fcoe_check_wait_queue(struct fc_lport *, struct sk_buff *);
 static int fcoe_device_notification(struct notifier_block *, ulong, void *);
 static void fcoe_dev_setup(void);
 static void fcoe_dev_cleanup(void);
+static struct fcoe_interface *
+       fcoe_hostlist_lookup_port(const struct net_device *dev);
 
 /* notification function from net device */
 static struct notifier_block fcoe_notifier = {
@@ -149,6 +153,7 @@ static int fcoe_fip_recv(struct sk_buff *skb, struct net_device *dev,
  * @netdev : ptr to the associated netdevice struct
  *
  * Returns : 0 for success
+ * Locking: must be called with the RTNL mutex held
  */
 static int fcoe_interface_setup(struct fcoe_interface *fcoe,
                                struct net_device *netdev)
@@ -188,13 +193,11 @@ static int fcoe_interface_setup(struct fcoe_interface *fcoe,
         * or enter promiscuous mode if not capable of listening
         * for multiple unicast MACs.
         */
-       rtnl_lock();
        memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN);
        dev_unicast_add(netdev, flogi_maddr);
        if (fip->spma)
                dev_unicast_add(netdev, fip->ctl_src_addr);
        dev_mc_add(netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
-       rtnl_unlock();
 
        /*
         * setup the receive function from ethernet driver
@@ -215,6 +218,7 @@ static int fcoe_interface_setup(struct fcoe_interface *fcoe,
 
 static void fcoe_fip_send(struct fcoe_ctlr *fip, struct sk_buff *skb);
 static void fcoe_update_src_mac(struct fcoe_ctlr *fip, u8 *old, u8 *new);
+static void fcoe_destroy_work(struct work_struct *work);
 
 /**
  * fcoe_interface_create()
@@ -232,6 +236,7 @@ static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev)
                return NULL;
        }
 
+       dev_hold(netdev);
        kref_init(&fcoe->kref);
 
        /*
@@ -249,6 +254,8 @@ static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev)
 /**
  * fcoe_interface_cleanup() - clean up netdev configurations
  * @fcoe:
+ *
+ * Caller must be holding the RTNL mutex
  */
 void fcoe_interface_cleanup(struct fcoe_interface *fcoe)
 {
@@ -266,11 +273,7 @@ void fcoe_interface_cleanup(struct fcoe_interface *fcoe)
        __dev_remove_pack(&fcoe->fip_packet_type);
        synchronize_net();
 
-       /* tear-down the FCoE controller */
-       fcoe_ctlr_destroy(&fcoe->ctlr);
-
        /* Delete secondary MAC addresses */
-       rtnl_lock();
        memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN);
        dev_unicast_delete(netdev, flogi_maddr);
        if (!is_zero_ether_addr(fip->data_src_addr))
@@ -278,7 +281,6 @@ void fcoe_interface_cleanup(struct fcoe_interface *fcoe)
        if (fip->spma)
                dev_unicast_delete(netdev, fip->ctl_src_addr);
        dev_mc_delete(netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
-       rtnl_unlock();
 }
 
 /**
@@ -288,10 +290,14 @@ void fcoe_interface_cleanup(struct fcoe_interface *fcoe)
 static void fcoe_interface_release(struct kref *kref)
 {
        struct fcoe_interface *fcoe;
+       struct net_device *netdev;
 
        fcoe = container_of(kref, struct fcoe_interface, kref);
-       fcoe_interface_cleanup(fcoe);
+       netdev = fcoe->netdev;
+       /* tear-down the FCoE controller */
+       fcoe_ctlr_destroy(&fcoe->ctlr);
        kfree(fcoe);
+       dev_put(netdev);
 }
 
 /**
@@ -524,8 +530,6 @@ bool fcoe_oem_match(struct fc_frame *fp)
  * fcoe_em_config() - allocates em for this lport
  * @lp: the fcoe that em is to allocated for
  *
- * Called with write fcoe_hostlist_lock held.
- *
  * Returns : 0 on success
  */
 static inline int fcoe_em_config(struct fc_lport *lp)
@@ -642,8 +646,7 @@ static void fcoe_if_destroy(struct fc_lport *lport)
        /* Free memory used by statistical counters */
        fc_lport_free_stats(lport);
 
-       /* Release the net_device and Scsi_Host */
-       dev_put(netdev);
+       /* Release the Scsi_Host */
        scsi_host_put(lport->host);
 }
 
@@ -718,7 +721,9 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe,
        }
        lport = shost_priv(shost);
        port = lport_priv(lport);
+       port->lport = lport;
        port->fcoe = fcoe;
+       INIT_WORK(&port->destroy_work, fcoe_destroy_work);
 
        /* configure fc_lport, e.g., em */
        rc = fcoe_lport_config(lport);
@@ -769,7 +774,6 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe,
                goto out_lp_destroy;
        }
 
-       dev_hold(netdev);
        fcoe_interface_get(fcoe);
        return lport;
 
@@ -826,7 +830,7 @@ static void fcoe_percpu_thread_create(unsigned int cpu)
        thread = kthread_create(fcoe_percpu_receive_thread,
                                (void *)p, "fcoethread/%d", cpu);
 
-       if (likely(!IS_ERR(p->thread))) {
+       if (likely(!IS_ERR(thread))) {
                kthread_bind(thread, cpu);
                wake_up_process(thread);
 
@@ -1298,6 +1302,15 @@ int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp)
        return 0;
 }
 
+/**
+ * fcoe_percpu_flush_done() - Indicate percpu queue flush completion.
+ * @skb: the skb being completed.
+ */
+static void fcoe_percpu_flush_done(struct sk_buff *skb)
+{
+       complete(&fcoe_flush_completion);
+}
+
 /**
  * fcoe_percpu_receive_thread() - recv thread per cpu
  * @arg: ptr to the fcoe per cpu struct
@@ -1337,7 +1350,8 @@ int fcoe_percpu_receive_thread(void *arg)
                fr = fcoe_dev_from_skb(skb);
                lp = fr->fr_dev;
                if (unlikely(lp == NULL)) {
-                       FCOE_NETDEV_DBG(skb->dev, "Invalid HBA Structure");
+                       if (skb->destructor != fcoe_percpu_flush_done)
+                               FCOE_NETDEV_DBG(skb->dev, "NULL lport in skb");
                        kfree_skb(skb);
                        continue;
                }
@@ -1530,19 +1544,18 @@ static int fcoe_device_notification(struct notifier_block *notifier,
        struct fc_lport *lp = NULL;
        struct net_device *netdev = ptr;
        struct fcoe_interface *fcoe;
+       struct fcoe_port *port;
        struct fcoe_dev_stats *stats;
        u32 link_possible = 1;
        u32 mfs;
        int rc = NOTIFY_OK;
 
-       read_lock(&fcoe_hostlist_lock);
        list_for_each_entry(fcoe, &fcoe_hostlist, list) {
                if (fcoe->netdev == netdev) {
                        lp = fcoe->ctlr.lp;
                        break;
                }
        }
-       read_unlock(&fcoe_hostlist_lock);
        if (lp == NULL) {
                rc = NOTIFY_DONE;
                goto out;
@@ -1564,6 +1577,13 @@ static int fcoe_device_notification(struct notifier_block *notifier,
                break;
        case NETDEV_REGISTER:
                break;
+       case NETDEV_UNREGISTER:
+               list_del(&fcoe->list);
+               port = lport_priv(fcoe->ctlr.lp);
+               fcoe_interface_cleanup(fcoe);
+               schedule_work(&port->destroy_work);
+               goto out;
+               break;
        default:
                FCOE_NETDEV_DBG(netdev, "Unknown event %ld "
                                "from netdev netlink\n", event);
@@ -1600,75 +1620,6 @@ static struct net_device *fcoe_if_to_netdev(const char *buffer)
        return NULL;
 }
 
-/**
- * fcoe_netdev_to_module_owner() - finds out the driver module of the netdev
- * @netdev: the target netdev
- *
- * Returns: ptr to the struct module, NULL for failure
- */
-static struct module *
-fcoe_netdev_to_module_owner(const struct net_device *netdev)
-{
-       struct device *dev;
-
-       if (!netdev)
-               return NULL;
-
-       dev = netdev->dev.parent;
-       if (!dev)
-               return NULL;
-
-       if (!dev->driver)
-               return NULL;
-
-       return dev->driver->owner;
-}
-
-/**
- * fcoe_ethdrv_get() - Hold the Ethernet driver
- * @netdev: the target netdev
- *
- * Holds the Ethernet driver module by try_module_get() for
- * the corresponding netdev.
- *
- * Returns: 0 for success
- */
-static int fcoe_ethdrv_get(const struct net_device *netdev)
-{
-       struct module *owner;
-
-       owner = fcoe_netdev_to_module_owner(netdev);
-       if (owner) {
-               FCOE_NETDEV_DBG(netdev, "Hold driver module %s\n",
-                               module_name(owner));
-               return  try_module_get(owner);
-       }
-       return -ENODEV;
-}
-
-/**
- * fcoe_ethdrv_put() - Release the Ethernet driver
- * @netdev: the target netdev
- *
- * Releases the Ethernet driver module by module_put for
- * the corresponding netdev.
- *
- * Returns: 0 for success
- */
-static int fcoe_ethdrv_put(const struct net_device *netdev)
-{
-       struct module *owner;
-
-       owner = fcoe_netdev_to_module_owner(netdev);
-       if (owner) {
-               FCOE_NETDEV_DBG(netdev, "Release driver module %s\n",
-                               module_name(owner));
-               module_put(owner);
-               return 0;
-       }
-       return -ENODEV;
-}
-
 /**
  * fcoe_destroy() - handles the destroy from sysfs
  * @buffer: expected to be an eth if name
@@ -1678,10 +1629,8 @@ static int fcoe_ethdrv_put(const struct net_device *netdev)
  */
 static int fcoe_destroy(const char *buffer, struct kernel_param *kp)
 {
-       struct net_device *netdev;
        struct fcoe_interface *fcoe;
-       struct fcoe_port *port;
-       struct fc_lport *lport;
+       struct net_device *netdev;
        int rc;
 
        mutex_lock(&fcoe_config_mutex);
@@ -1702,19 +1651,18 @@ static int fcoe_destroy(const char *buffer, struct kernel_param *kp)
                rc = -ENODEV;
                goto out_nodev;
        }
-       /* look for existing lport */
-       lport = fcoe_hostlist_lookup(netdev);
-       if (!lport) {
+
+       rtnl_lock();
+       fcoe = fcoe_hostlist_lookup_port(netdev);
+       if (!fcoe) {
+               rtnl_unlock();
                rc = -ENODEV;
                goto out_putdev;
        }
-       /* Remove the instance from fcoe's list */
-       fcoe_hostlist_remove(lport);
-       port = lport_priv(lport);
-       fcoe = port->fcoe;
-       fcoe_if_destroy(lport);
-       fcoe_ethdrv_put(netdev);
-       rc = 0;
+       list_del(&fcoe->list);
+       fcoe_interface_cleanup(fcoe);
+       rtnl_unlock();
+       fcoe_if_destroy(fcoe->ctlr.lp);
 out_putdev:
        dev_put(netdev);
 out_nodev:
@@ -1722,6 +1670,16 @@ out_nodev:
        return rc;
 }
 
+static void fcoe_destroy_work(struct work_struct *work)
+{
+       struct fcoe_port *port;
+
+       port = container_of(work, struct fcoe_port, destroy_work);
+       mutex_lock(&fcoe_config_mutex);
+       fcoe_if_destroy(port->lport);
+       mutex_unlock(&fcoe_config_mutex);
+}
+
 /**
  * fcoe_create() - Handles the create call from sysfs
  * @buffer: expected to be an eth if name
@@ -1749,17 +1707,18 @@ static int fcoe_create(const char *buffer, struct kernel_param *kp)
        }
 #endif
 
+       rtnl_lock();
        netdev = fcoe_if_to_netdev(buffer);
        if (!netdev) {
                rc = -ENODEV;
                goto out_nodev;
        }
+
        /* look for existing lport */
        if (fcoe_hostlist_lookup(netdev)) {
                rc = -EEXIST;
                goto out_putdev;
        }
-       fcoe_ethdrv_get(netdev);
 
        fcoe = fcoe_interface_create(netdev);
        if (!fcoe) {
@@ -1771,8 +1730,8 @@ static int fcoe_create(const char *buffer, struct kernel_param *kp)
        if (IS_ERR(lport)) {
                printk(KERN_ERR "fcoe: Failed to create interface (%s)\n",
                       netdev->name);
-               fcoe_ethdrv_put(netdev);
                rc = -EIO;
+               fcoe_interface_cleanup(fcoe);
                goto out_free;
        }
 
@@ -1798,6 +1757,7 @@ out_free:
 out_putdev:
        dev_put(netdev);
 out_nodev:
+       rtnl_unlock();
        mutex_unlock(&fcoe_config_mutex);
        return rc;
 }
@@ -1852,6 +1812,13 @@ int fcoe_link_ok(struct fc_lport *lp)
 /**
  * fcoe_percpu_clean() - Clear the pending skbs for an lport
  * @lp: the fc_lport
+ *
+ * Must be called with fcoe_create_mutex held to single-thread completion.
+ *
+ * This flushes the pending skbs by adding a new skb to each queue and
+ * waiting until they are all freed.  This assures us that not only are
+ * there no packets that will be handled by the lport, but also that any
+ * threads already handling packet have returned.
  */
 void fcoe_percpu_clean(struct fc_lport *lp)
 {
@@ -1876,7 +1843,25 @@ void fcoe_percpu_clean(struct fc_lport *lp)
                                kfree_skb(skb);
                        }
                }
+
+               if (!pp->thread || !cpu_online(cpu)) {
+                       spin_unlock_bh(&pp->fcoe_rx_list.lock);
+                       continue;
+               }
+
+               skb = dev_alloc_skb(0);
+               if (!skb) {
+                       spin_unlock_bh(&pp->fcoe_rx_list.lock);
+                       continue;
+               }
+               skb->destructor = fcoe_percpu_flush_done;
+
+               __skb_queue_tail(&pp->fcoe_rx_list, skb);
+               if (pp->fcoe_rx_list.qlen == 1)
+                       wake_up_process(pp->thread);
                spin_unlock_bh(&pp->fcoe_rx_list.lock);
+
+               wait_for_completion(&fcoe_flush_completion);
        }
 }
 
@@ -1917,9 +1902,8 @@ int fcoe_reset(struct Scsi_Host *shost)
  * fcoe_hostlist_lookup_port() - find the corresponding lport by a given device
  * @dev: this is currently ptr to net_device
  *
- * Called with fcoe_hostlist_lock held.
- *
  * Returns: NULL or the located fcoe_port
+ * Locking: must be called with the RNL mutex held
  */
 static struct fcoe_interface *
 fcoe_hostlist_lookup_port(const struct net_device *dev)
@@ -1938,15 +1922,13 @@ fcoe_hostlist_lookup_port(const struct net_device *dev)
  * @netdev: ptr to net_device
  *
  * Returns: 0 for success
+ * Locking: must be called with the RTNL mutex held
  */
-struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev)
+static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev)
 {
        struct fcoe_interface *fcoe;
 
-       read_lock(&fcoe_hostlist_lock);
        fcoe = fcoe_hostlist_lookup_port(netdev);
-       read_unlock(&fcoe_hostlist_lock);
-
        return (fcoe) ? fcoe->ctlr.lp : NULL;
 }
 
@@ -1955,39 +1937,19 @@ struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev)
  * @lp: ptr to the fc_lport to be added
  *
  * Returns: 0 for success
+ * Locking: must be called with the RTNL mutex held
  */
-int fcoe_hostlist_add(const struct fc_lport *lport)
+static int fcoe_hostlist_add(const struct fc_lport *lport)
 {
        struct fcoe_interface *fcoe;
        struct fcoe_port *port;
 
-       write_lock_bh(&fcoe_hostlist_lock);
        fcoe = fcoe_hostlist_lookup_port(fcoe_netdev(lport));
        if (!fcoe) {
                port = lport_priv(lport);
                fcoe = port->fcoe;
                list_add_tail(&fcoe->list, &fcoe_hostlist);
        }
-       write_unlock_bh(&fcoe_hostlist_lock);
-       return 0;
-}
-
-/**
- * fcoe_hostlist_remove() - remove a lport from lports list
- * @lp: ptr to the fc_lport to be removed
- *
- * Returns: 0 for success
- */
-int fcoe_hostlist_remove(const struct fc_lport *lport)
-{
-       struct fcoe_interface *fcoe;
-
-       write_lock_bh(&fcoe_hostlist_lock);
-       fcoe = fcoe_hostlist_lookup_port(fcoe_netdev(lport));
-       BUG_ON(!fcoe);
-       list_del(&fcoe->list);
-       write_unlock_bh(&fcoe_hostlist_lock);
-
        return 0;
 }
 
@@ -2045,30 +2007,35 @@ static void __exit fcoe_exit(void)
 {
        unsigned int cpu;
        struct fcoe_interface *fcoe, *tmp;
-       LIST_HEAD(local_list);
+       struct fcoe_port *port;
 
        mutex_lock(&fcoe_config_mutex);
 
        fcoe_dev_cleanup();
 
        /* releases the associated fcoe hosts */
-       write_lock_bh(&fcoe_hostlist_lock);
-       list_splice_init(&fcoe_hostlist, &local_list);
-       write_unlock_bh(&fcoe_hostlist_lock);
-
-       list_for_each_entry_safe(fcoe, tmp, &local_list, list) {
+       rtnl_lock();
+       list_for_each_entry_safe(fcoe, tmp, &fcoe_hostlist, list) {
                list_del(&fcoe->list);
-               fcoe_if_destroy(fcoe->ctlr.lp);
+               port = lport_priv(fcoe->ctlr.lp);
+               fcoe_interface_cleanup(fcoe);
+               schedule_work(&port->destroy_work);
        }
+       rtnl_unlock();
 
        unregister_hotcpu_notifier(&fcoe_cpu_notifier);
 
        for_each_online_cpu(cpu)
                fcoe_percpu_thread_destroy(cpu);
 
-       /* detach from scsi transport */
-       fcoe_if_exit();
-
        mutex_unlock(&fcoe_config_mutex);
+
+       /* flush any asyncronous interface destroys,
+        * this should happen after the netdev notifier is unregistered */
+       flush_scheduled_work();
+
+       /* detach from scsi transport
+        * must happen after all destroys are done, therefor after the flush */
+       fcoe_if_exit();
 }
 module_exit(fcoe_exit);