MODULE_PARM_DESC(ddp_min, "Minimum I/O size in bytes for " \
"Direct Data Placement (DDP).");
+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 */
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 = {
.max_sectors = 0xffff,
};
+static int fcoe_fip_recv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *ptype,
+ struct net_device *orig_dev);
+/**
+ * fcoe_interface_setup()
+ * @fcoe: new fcoe_interface
+ * @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)
+{
+ struct fcoe_ctlr *fip = &fcoe->ctlr;
+ struct netdev_hw_addr *ha;
+ u8 flogi_maddr[ETH_ALEN];
+
+ fcoe->netdev = netdev;
+
+ /* Do not support for bonding device */
+ if ((netdev->priv_flags & IFF_MASTER_ALB) ||
+ (netdev->priv_flags & IFF_SLAVE_INACTIVE) ||
+ (netdev->priv_flags & IFF_MASTER_8023AD)) {
+ return -EOPNOTSUPP;
+ }
+
+ /* look for SAN MAC address, if multiple SAN MACs exist, only
+ * use the first one for SPMA */
+ rcu_read_lock();
+ for_each_dev_addr(netdev, ha) {
+ if ((ha->type == NETDEV_HW_ADDR_T_SAN) &&
+ (is_valid_ether_addr(fip->ctl_src_addr))) {
+ memcpy(fip->ctl_src_addr, ha->addr, ETH_ALEN);
+ fip->spma = 1;
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ /* setup Source Mac Address */
+ if (!fip->spma)
+ memcpy(fip->ctl_src_addr, netdev->dev_addr, netdev->addr_len);
+
+ /*
+ * Add FCoE MAC address as second unicast MAC address
+ * or enter promiscuous mode if not capable of listening
+ * for multiple unicast MACs.
+ */
+ 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);
+
+ /*
+ * setup the receive function from ethernet driver
+ * on the ethertype for the given device
+ */
+ fcoe->fcoe_packet_type.func = fcoe_rcv;
+ fcoe->fcoe_packet_type.type = __constant_htons(ETH_P_FCOE);
+ fcoe->fcoe_packet_type.dev = netdev;
+ dev_add_pack(&fcoe->fcoe_packet_type);
+
+ fcoe->fip_packet_type.func = fcoe_fip_recv;
+ fcoe->fip_packet_type.type = htons(ETH_P_FIP);
+ fcoe->fip_packet_type.dev = netdev;
+ dev_add_pack(&fcoe->fip_packet_type);
+
+ return 0;
+}
+
+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()
+ * @netdev: network interface
+ *
+ * Returns: pointer to a struct fcoe_interface or NULL on error
+ */
+static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev)
+{
+ struct fcoe_interface *fcoe;
+
+ fcoe = kzalloc(sizeof(*fcoe), GFP_KERNEL);
+ if (!fcoe) {
+ FCOE_NETDEV_DBG(netdev, "Could not allocate fcoe structure\n");
+ return NULL;
+ }
+
+ dev_hold(netdev);
+ kref_init(&fcoe->kref);
+
+ /*
+ * Initialize FIP.
+ */
+ fcoe_ctlr_init(&fcoe->ctlr);
+ fcoe->ctlr.send = fcoe_fip_send;
+ fcoe->ctlr.update_mac = fcoe_update_src_mac;
+
+ fcoe_interface_setup(fcoe, netdev);
+
+ return fcoe;
+}
+
+/**
+ * fcoe_interface_cleanup() - clean up netdev configurations
+ * @fcoe:
+ *
+ * Caller must be holding the RTNL mutex
+ */
+void fcoe_interface_cleanup(struct fcoe_interface *fcoe)
+{
+ struct net_device *netdev = fcoe->netdev;
+ struct fcoe_ctlr *fip = &fcoe->ctlr;
+ u8 flogi_maddr[ETH_ALEN];
+
+ /*
+ * Don't listen for Ethernet packets anymore.
+ * synchronize_net() ensures that the packet handlers are not running
+ * on another CPU. dev_remove_pack() would do that, this calls the
+ * unsyncronized version __dev_remove_pack() to avoid multiple delays.
+ */
+ __dev_remove_pack(&fcoe->fcoe_packet_type);
+ __dev_remove_pack(&fcoe->fip_packet_type);
+ synchronize_net();
+
+ /* Delete secondary MAC addresses */
+ 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))
+ dev_unicast_delete(netdev, fip->data_src_addr);
+ if (fip->spma)
+ dev_unicast_delete(netdev, fip->ctl_src_addr);
+ dev_mc_delete(netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
+}
+
+/**
+ * fcoe_interface_release() - fcoe_port kref release function
+ * @kref: embedded reference count in an fcoe_interface struct
+ */
+static void fcoe_interface_release(struct kref *kref)
+{
+ struct fcoe_interface *fcoe;
+ struct net_device *netdev;
+
+ fcoe = container_of(kref, struct fcoe_interface, kref);
+ netdev = fcoe->netdev;
+ /* tear-down the FCoE controller */
+ fcoe_ctlr_destroy(&fcoe->ctlr);
+ kfree(fcoe);
+ dev_put(netdev);
+}
+
+/**
+ * fcoe_interface_get()
+ * @fcoe:
+ */
+static inline void fcoe_interface_get(struct fcoe_interface *fcoe)
+{
+ kref_get(&fcoe->kref);
+}
+
+/**
+ * fcoe_interface_put()
+ * @fcoe:
+ */
+static inline void fcoe_interface_put(struct fcoe_interface *fcoe)
+{
+ kref_put(&fcoe->kref, fcoe_interface_release);
+}
+
/**
* fcoe_fip_recv - handle a received FIP frame.
* @skb: the receive skb
return 0;
}
-/**
- * fcoe_netdev_cleanup() - clean up netdev configurations
- * @port: ptr to the fcoe_port
- */
-void fcoe_netdev_cleanup(struct fcoe_port *port)
-{
- u8 flogi_maddr[ETH_ALEN];
- struct fcoe_interface *fcoe = port->fcoe;
-
- /* Don't listen for Ethernet packets anymore */
- dev_remove_pack(&fcoe->fcoe_packet_type);
- dev_remove_pack(&fcoe->fip_packet_type);
-
- /* Delete secondary MAC addresses */
- rtnl_lock();
- memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN);
- dev_unicast_delete(fcoe->netdev, flogi_maddr);
- if (!is_zero_ether_addr(fcoe->ctlr.data_src_addr))
- dev_unicast_delete(fcoe->netdev, fcoe->ctlr.data_src_addr);
- if (fcoe->ctlr.spma)
- dev_unicast_delete(fcoe->netdev, fcoe->ctlr.ctl_src_addr);
- dev_mc_delete(fcoe->netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
- rtnl_unlock();
-}
-
/**
* fcoe_queue_timer() - fcoe queue timer
* @lp: the fc_lport pointer
u64 wwnn, wwpn;
struct fcoe_interface *fcoe;
struct fcoe_port *port;
- u8 flogi_maddr[ETH_ALEN];
- struct netdev_hw_addr *ha;
/* Setup lport private data to point to fcoe softc */
port = lport_priv(lp);
fcoe = port->fcoe;
- fcoe->ctlr.lp = lp;
- fcoe->netdev = netdev;
-
- /* Do not support for bonding device */
- if ((netdev->priv_flags & IFF_MASTER_ALB) ||
- (netdev->priv_flags & IFF_SLAVE_INACTIVE) ||
- (netdev->priv_flags & IFF_MASTER_8023AD)) {
- return -EOPNOTSUPP;
- }
/*
* Determine max frame size based on underlying device and optional
port->fcoe_pending_queue_active = 0;
setup_timer(&port->timer, fcoe_queue_timer, (unsigned long)lp);
- /* look for SAN MAC address, if multiple SAN MACs exist, only
- * use the first one for SPMA */
- rcu_read_lock();
- for_each_dev_addr(netdev, ha) {
- if ((ha->type == NETDEV_HW_ADDR_T_SAN) &&
- (is_valid_ether_addr(fcoe->ctlr.ctl_src_addr))) {
- memcpy(fcoe->ctlr.ctl_src_addr, ha->addr, ETH_ALEN);
- fcoe->ctlr.spma = 1;
- break;
- }
- }
- rcu_read_unlock();
-
- /* setup Source Mac Address */
- if (!fcoe->ctlr.spma)
- memcpy(fcoe->ctlr.ctl_src_addr, netdev->dev_addr,
- netdev->addr_len);
-
wwnn = fcoe_wwn_from_mac(netdev->dev_addr, 1, 0);
fc_set_wwnn(lp, wwnn);
/* XXX - 3rd arg needs to be vlan id */
wwpn = fcoe_wwn_from_mac(netdev->dev_addr, 2, 0);
fc_set_wwpn(lp, wwpn);
- /*
- * Add FCoE MAC address as second unicast MAC address
- * 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 (fcoe->ctlr.spma)
- dev_unicast_add(netdev, fcoe->ctlr.ctl_src_addr);
- dev_mc_add(netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
- rtnl_unlock();
-
- /*
- * setup the receive function from ethernet driver
- * on the ethertype for the given device
- */
- fcoe->fcoe_packet_type.func = fcoe_rcv;
- fcoe->fcoe_packet_type.type = __constant_htons(ETH_P_FCOE);
- fcoe->fcoe_packet_type.dev = netdev;
- dev_add_pack(&fcoe->fcoe_packet_type);
-
- fcoe->fip_packet_type.func = fcoe_fip_recv;
- fcoe->fip_packet_type.type = htons(ETH_P_FIP);
- fcoe->fip_packet_type.dev = netdev;
- dev_add_pack(&fcoe->fip_packet_type);
-
return 0;
}
* 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)
{
struct fcoe_port *port = lport_priv(lp);
- struct fcoe_port *oldport = NULL;
struct fcoe_interface *fcoe = port->fcoe;
struct fcoe_interface *oldfcoe = NULL;
struct net_device *old_real_dev, *cur_real_dev;
cur_real_dev = fcoe->netdev;
list_for_each_entry(oldfcoe, &fcoe_hostlist, list) {
- oldport = oldfcoe->priv;
if (oldfcoe->netdev->priv_flags & IFF_802_1Q_VLAN)
old_real_dev = vlan_dev_real_dev(oldfcoe->netdev);
else
old_real_dev = oldfcoe->netdev;
if (cur_real_dev == old_real_dev) {
- port->oem = oldport->oem;
+ fcoe->oem = oldfcoe->oem;
break;
}
}
- if (port->oem) {
- if (!fc_exch_mgr_add(lp, port->oem, fcoe_oem_match)) {
+ if (fcoe->oem) {
+ if (!fc_exch_mgr_add(lp, fcoe->oem, fcoe_oem_match)) {
printk(KERN_ERR "fcoe_em_config: failed to add "
"offload em:%p on interface:%s\n",
- port->oem, fcoe->netdev->name);
+ fcoe->oem, fcoe->netdev->name);
return -ENOMEM;
}
} else {
- port->oem = fc_exch_mgr_alloc(lp, FC_CLASS_3,
+ fcoe->oem = fc_exch_mgr_alloc(lp, FC_CLASS_3,
FCOE_MIN_XID, lp->lro_xid,
fcoe_oem_match);
- if (!port->oem) {
+ if (!fcoe->oem) {
printk(KERN_ERR "fcoe_em_config: failed to allocate "
"em for offload exches on interface:%s\n",
fcoe->netdev->name);
/* Logout of the fabric */
fc_fabric_logoff(lport);
- /* Remove the instance from fcoe's list */
- fcoe_hostlist_remove(lport);
+ /* Cleanup the fc_lport */
+ fc_lport_destroy(lport);
+ fc_fcp_destroy(lport);
- /* clean up netdev configurations */
- fcoe_netdev_cleanup(port);
+ /* Stop the transmit retry timer */
+ del_timer_sync(&port->timer);
- /* tear-down the FCoE controller */
- fcoe_ctlr_destroy(&fcoe->ctlr);
+ /* Free existing transmit skbs */
+ fcoe_clean_pending_queue(lport);
+
+ /* receives may not be stopped until after this */
+ fcoe_interface_put(fcoe);
/* Free queued packets for the per-CPU receive threads */
fcoe_percpu_clean(lport);
- /* Cleanup the fc_lport */
- fc_lport_destroy(lport);
- fc_fcp_destroy(lport);
-
/* Detach from the scsi-ml */
fc_remove_host(lport->host);
scsi_remove_host(lport->host);
/* There are no more rports or I/O, free the EM */
fc_exch_mgr_free(lport);
- /* Free existing skbs */
- fcoe_clean_pending_queue(lport);
-
- /* Stop the timer */
- del_timer_sync(&port->timer);
-
/* 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);
- kfree(fcoe); /* TODO, should be refcounted */
}
/*
};
/**
- * fcoe_if_create() - this function creates the fcoe interface
- * @netdev: pointer the associated netdevice
+ * fcoe_if_create() - this function creates the fcoe port
+ * @fcoe: fcoe_interface structure to create an fc_lport instance on
* @parent: device pointer to be the parent in sysfs for the SCSI host
*
- * Creates fc_lport struct and scsi_host for lport, configures lport
- * and starts fabric login.
+ * Creates fc_lport struct and scsi_host for lport, configures lport.
*
* Returns : The allocated fc_lport or an error pointer
*/
-static struct fc_lport *fcoe_if_create(struct net_device *netdev,
+static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe,
struct device *parent)
{
int rc;
struct fc_lport *lport = NULL;
struct fcoe_port *port;
- struct fcoe_interface *fcoe;
struct Scsi_Host *shost;
+ struct net_device *netdev = fcoe->netdev;
FCOE_NETDEV_DBG(netdev, "Create Interface\n");
- fcoe = kzalloc(sizeof(*fcoe), GFP_KERNEL);
- if (!fcoe) {
- FCOE_NETDEV_DBG(netdev, "Could not allocate fcoe structure\n");
- rc = -ENOMEM;
- goto out;
- }
-
shost = libfc_host_alloc(&fcoe_shost_template,
sizeof(struct fcoe_port));
if (!shost) {
FCOE_NETDEV_DBG(netdev, "Could not allocate host structure\n");
rc = -ENOMEM;
- goto out_kfree_port;
+ goto out;
}
lport = shost_priv(shost);
port = lport_priv(lport);
-
+ port->lport = lport;
port->fcoe = fcoe;
- fcoe->priv = port;
+ INIT_WORK(&port->destroy_work, fcoe_destroy_work);
/* configure fc_lport, e.g., em */
rc = fcoe_lport_config(lport);
goto out_host_put;
}
- /*
- * Initialize FIP.
- */
- fcoe_ctlr_init(&fcoe->ctlr);
- fcoe->ctlr.send = fcoe_fip_send;
- fcoe->ctlr.update_mac = fcoe_update_src_mac;
-
/* configure lport network properties */
rc = fcoe_netdev_config(lport, netdev);
if (rc) {
FCOE_NETDEV_DBG(netdev, "Could not configure netdev for the "
"interface\n");
- goto out_netdev_cleanup;
+ goto out_lp_destroy;
}
/* configure lport scsi host properties */
if (rc) {
FCOE_NETDEV_DBG(netdev, "Could not configure shost for the "
"interface\n");
- goto out_netdev_cleanup;
+ goto out_lp_destroy;
}
/* Initialize the library */
/*
* fcoe_em_alloc() and fcoe_hostlist_add() both
- * need to be atomic under fcoe_hostlist_lock
+ * need to be atomic with respect to other changes to the hostlist
* since fcoe_em_alloc() looks for an existing EM
* instance on host list updated by fcoe_hostlist_add().
+ *
+ * This is currently handled through the fcoe_config_mutex begin held.
*/
- write_lock(&fcoe_hostlist_lock);
+
/* lport exch manager allocation */
rc = fcoe_em_config(lport);
if (rc) {
goto out_lp_destroy;
}
- /* add to lports list */
- fcoe_hostlist_add(lport);
- write_unlock(&fcoe_hostlist_lock);
-
- lport->boot_time = jiffies;
-
- fc_fabric_login(lport);
-
- if (!fcoe_link_ok(lport))
- fcoe_ctlr_link_up(&fcoe->ctlr);
-
- dev_hold(netdev);
-
+ fcoe_interface_get(fcoe);
return lport;
out_lp_destroy:
fc_exch_mgr_free(lport);
-out_netdev_cleanup:
- fcoe_netdev_cleanup(port);
out_host_put:
scsi_host_put(lport->host);
-out_kfree_port:
- kfree(fcoe);
out:
return ERR_PTR(rc);
}
int __exit fcoe_if_exit(void)
{
fc_release_transport(scsi_transport_fcoe_sw);
+ scsi_transport_fcoe_sw = NULL;
return 0;
}
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);
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
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;
}
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;
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);
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
*/
static int fcoe_destroy(const char *buffer, struct kernel_param *kp)
{
+ struct fcoe_interface *fcoe;
struct net_device *netdev;
- struct fc_lport *lport;
int rc;
+ mutex_lock(&fcoe_config_mutex);
+#ifdef CONFIG_FCOE_MODULE
+ /*
+ * Make sure the module has been initialized, and is not about to be
+ * removed. Module paramter sysfs files are writable before the
+ * module_init function is called and after module_exit.
+ */
+ if (THIS_MODULE->state != MODULE_STATE_LIVE) {
+ rc = -ENODEV;
+ goto out_nodev;
+ }
+#endif
+
netdev = fcoe_if_to_netdev(buffer);
if (!netdev) {
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;
}
- 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:
+ mutex_unlock(&fcoe_config_mutex);
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
static int fcoe_create(const char *buffer, struct kernel_param *kp)
{
int rc;
+ struct fcoe_interface *fcoe;
struct fc_lport *lport;
struct net_device *netdev;
+ mutex_lock(&fcoe_config_mutex);
+#ifdef CONFIG_FCOE_MODULE
+ /*
+ * Make sure the module has been initialized, and is not about to be
+ * removed. Module paramter sysfs files are writable before the
+ * module_init function is called and after module_exit.
+ */
+ if (THIS_MODULE->state != MODULE_STATE_LIVE) {
+ rc = -ENODEV;
+ goto out_nodev;
+ }
+#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);
- lport = fcoe_if_create(netdev, &netdev->dev);
+ fcoe = fcoe_interface_create(netdev);
+ if (!fcoe) {
+ rc = -ENOMEM;
+ goto out_putdev;
+ }
+
+ lport = fcoe_if_create(fcoe, &netdev->dev);
if (IS_ERR(lport)) {
printk(KERN_ERR "fcoe: Failed to create interface (%s)\n",
netdev->name);
- fcoe_ethdrv_put(netdev);
rc = -EIO;
- goto out_putdev;
+ fcoe_interface_cleanup(fcoe);
+ goto out_free;
}
+
+ /* Make this the "master" N_Port */
+ fcoe->ctlr.lp = lport;
+
+ /* add to lports list */
+ fcoe_hostlist_add(lport);
+
+ /* start FIP Discovery and FLOGI */
+ lport->boot_time = jiffies;
+ fc_fabric_login(lport);
+ if (!fcoe_link_ok(lport))
+ fcoe_ctlr_link_up(&fcoe->ctlr);
+
rc = 0;
+out_free:
+ /*
+ * Release from init in fcoe_interface_create(), on success lport
+ * should be holding a reference taken in fcoe_if_create().
+ */
+ fcoe_interface_put(fcoe);
out_putdev:
dev_put(netdev);
out_nodev:
+ rtnl_unlock();
+ mutex_unlock(&fcoe_config_mutex);
return rc;
}
/**
* 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)
{
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);
}
}
* 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)
* @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;
}
* fcoe_hostlist_add() - Add a lport to lports list
* @lp: ptr to the fc_lport to be added
*
- * Called with write fcoe_hostlist_lock held.
- *
* 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;
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;
-}
-
/**
* fcoe_init() - fcoe module loading initialization
*
int rc = 0;
struct fcoe_percpu_s *p;
+ mutex_lock(&fcoe_config_mutex);
+
for_each_possible_cpu(cpu) {
p = &per_cpu(fcoe_percpu, cpu);
skb_queue_head_init(&p->fcoe_rx_list);
if (rc)
goto out_free;
+ mutex_unlock(&fcoe_config_mutex);
return 0;
out_free:
for_each_online_cpu(cpu) {
fcoe_percpu_thread_destroy(cpu);
}
-
+ mutex_unlock(&fcoe_config_mutex);
return rc;
}
module_init(fcoe_init);
{
unsigned int cpu;
struct fcoe_interface *fcoe, *tmp;
+ struct fcoe_port *port;
+
+ mutex_lock(&fcoe_config_mutex);
fcoe_dev_cleanup();
/* releases the associated fcoe hosts */
- list_for_each_entry_safe(fcoe, tmp, &fcoe_hostlist, list)
- fcoe_if_destroy(fcoe->ctlr.lp);
+ rtnl_lock();
+ list_for_each_entry_safe(fcoe, tmp, &fcoe_hostlist, list) {
+ list_del(&fcoe->list);
+ 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 */
+ 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);