diff options
Diffstat (limited to 'drivers/scsi/fcoe/fcoe.c')
| -rw-r--r-- | drivers/scsi/fcoe/fcoe.c | 2598 |
1 files changed, 1762 insertions, 836 deletions
diff --git a/drivers/scsi/fcoe/fcoe.c b/drivers/scsi/fcoe/fcoe.c index 704b8e03494..00ee0ed642a 100644 --- a/drivers/scsi/fcoe/fcoe.c +++ b/drivers/scsi/fcoe/fcoe.c @@ -18,7 +18,6 @@ */ #include <linux/module.h> -#include <linux/version.h> #include <linux/spinlock.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> @@ -26,10 +25,14 @@ #include <linux/if_ether.h> #include <linux/if_vlan.h> #include <linux/crc32.h> +#include <linux/slab.h> #include <linux/cpu.h> #include <linux/fs.h> #include <linux/sysfs.h> #include <linux/ctype.h> +#include <linux/workqueue.h> +#include <net/dcbnl.h> +#include <net/dcbevent.h> #include <scsi/scsi_tcq.h> #include <scsi/scsicam.h> #include <scsi/scsi_transport.h> @@ -38,6 +41,7 @@ #include <scsi/fc/fc_encaps.h> #include <scsi/fc/fc_fip.h> +#include <scsi/fc/fc_fcoe.h> #include <scsi/libfc.h> #include <scsi/fc_frame.h> @@ -50,55 +54,147 @@ MODULE_DESCRIPTION("FCoE"); MODULE_LICENSE("GPL v2"); /* Performance tuning parameters for fcoe */ -static unsigned int fcoe_ddp_min; +static unsigned int fcoe_ddp_min = 4096; module_param_named(ddp_min, fcoe_ddp_min, uint, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(ddp_min, "Minimum I/O size in bytes for " \ "Direct Data Placement (DDP)."); -DEFINE_MUTEX(fcoe_config_mutex); +unsigned int fcoe_debug_logging; +module_param_named(debug_logging, fcoe_debug_logging, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels"); + +static DEFINE_MUTEX(fcoe_config_mutex); + +static struct workqueue_struct *fcoe_wq; /* 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_PER_CPU(struct fcoe_percpu_s, fcoe_percpu); +static LIST_HEAD(fcoe_hostlist); +static DEFINE_PER_CPU(struct fcoe_percpu_s, fcoe_percpu); /* Function Prototypes */ -static int fcoe_reset(struct Scsi_Host *shost); +static int fcoe_reset(struct Scsi_Host *); static int fcoe_xmit(struct fc_lport *, struct fc_frame *); static int fcoe_rcv(struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *); -static int fcoe_percpu_receive_thread(void *arg); -static void fcoe_clean_pending_queue(struct fc_lport *lp); -static void fcoe_percpu_clean(struct fc_lport *lp); -static int fcoe_link_ok(struct fc_lport *lp); +static int fcoe_percpu_receive_thread(void *); +static void fcoe_percpu_clean(struct fc_lport *); +static int fcoe_link_ok(struct fc_lport *); static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *); static int fcoe_hostlist_add(const struct fc_lport *); +static void fcoe_hostlist_del(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 fcoe_interface +*fcoe_hostlist_lookup_port(const struct net_device *); + +static int fcoe_fip_recv(struct sk_buff *, struct net_device *, + struct packet_type *, struct net_device *); + +static void fcoe_fip_send(struct fcoe_ctlr *, struct sk_buff *); +static void fcoe_update_src_mac(struct fc_lport *, u8 *); +static u8 *fcoe_get_src_mac(struct fc_lport *); +static void fcoe_destroy_work(struct work_struct *); + +static int fcoe_ddp_setup(struct fc_lport *, u16, struct scatterlist *, + unsigned int); +static int fcoe_ddp_done(struct fc_lport *, u16); +static int fcoe_ddp_target(struct fc_lport *, u16, struct scatterlist *, + unsigned int); +static int fcoe_cpu_callback(struct notifier_block *, unsigned long, void *); +static int fcoe_dcb_app_notification(struct notifier_block *notifier, + ulong event, void *ptr); + +static bool fcoe_match(struct net_device *netdev); +static int fcoe_create(struct net_device *netdev, enum fip_state fip_mode); +static int fcoe_destroy(struct net_device *netdev); +static int fcoe_enable(struct net_device *netdev); +static int fcoe_disable(struct net_device *netdev); + +/* fcoe_syfs control interface handlers */ +static int fcoe_ctlr_alloc(struct net_device *netdev); +static int fcoe_ctlr_enabled(struct fcoe_ctlr_device *cdev); + + +static struct fc_seq *fcoe_elsct_send(struct fc_lport *, + u32 did, struct fc_frame *, + unsigned int op, + void (*resp)(struct fc_seq *, + struct fc_frame *, + void *), + void *, u32 timeout); +static void fcoe_recv_frame(struct sk_buff *skb); + +/* notification function for packets from net device */ static struct notifier_block fcoe_notifier = { .notifier_call = fcoe_device_notification, }; -static struct scsi_transport_template *scsi_transport_fcoe_sw; +/* notification function for CPU hotplug events */ +static struct notifier_block fcoe_cpu_notifier = { + .notifier_call = fcoe_cpu_callback, +}; + +/* notification function for DCB events */ +static struct notifier_block dcb_notifier = { + .notifier_call = fcoe_dcb_app_notification, +}; + +static struct scsi_transport_template *fcoe_nport_scsi_transport; +static struct scsi_transport_template *fcoe_vport_scsi_transport; + +static int fcoe_vport_destroy(struct fc_vport *); +static int fcoe_vport_create(struct fc_vport *, bool disabled); +static int fcoe_vport_disable(struct fc_vport *, bool disable); +static void fcoe_set_vport_symbolic_name(struct fc_vport *); +static void fcoe_set_port_id(struct fc_lport *, u32, struct fc_frame *); +static void fcoe_fcf_get_vlan_id(struct fcoe_fcf_device *); + +static struct fcoe_sysfs_function_template fcoe_sysfs_templ = { + .set_fcoe_ctlr_mode = fcoe_ctlr_set_fip_mode, + .set_fcoe_ctlr_enabled = fcoe_ctlr_enabled, + .get_fcoe_ctlr_link_fail = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_vlink_fail = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_miss_fka = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_symb_err = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_err_block = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_fcs_error = fcoe_ctlr_get_lesb, + + .get_fcoe_fcf_selected = fcoe_fcf_get_selected, + .get_fcoe_fcf_vlan_id = fcoe_fcf_get_vlan_id, +}; -struct fc_function_template fcoe_transport_function = { +static struct libfc_function_template fcoe_libfc_fcn_templ = { + .frame_send = fcoe_xmit, + .ddp_setup = fcoe_ddp_setup, + .ddp_done = fcoe_ddp_done, + .ddp_target = fcoe_ddp_target, + .elsct_send = fcoe_elsct_send, + .get_lesb = fcoe_get_lesb, + .lport_set_port_id = fcoe_set_port_id, +}; + +static struct fc_function_template fcoe_nport_fc_functions = { .show_host_node_name = 1, .show_host_port_name = 1, .show_host_supported_classes = 1, .show_host_supported_fc4s = 1, .show_host_active_fc4s = 1, .show_host_maxframe_size = 1, + .show_host_serial_number = 1, + .show_host_manufacturer = 1, + .show_host_model = 1, + .show_host_model_description = 1, + .show_host_hardware_version = 1, + .show_host_driver_version = 1, + .show_host_firmware_version = 1, + .show_host_optionrom_version = 1, .show_host_port_id = 1, .show_host_supported_speeds = 1, @@ -123,6 +219,56 @@ struct fc_function_template fcoe_transport_function = { .issue_fc_host_lip = fcoe_reset, .terminate_rport_io = fc_rport_terminate_io, + + .vport_create = fcoe_vport_create, + .vport_delete = fcoe_vport_destroy, + .vport_disable = fcoe_vport_disable, + .set_vport_symbolic_name = fcoe_set_vport_symbolic_name, + + .bsg_request = fc_lport_bsg_request, +}; + +static struct fc_function_template fcoe_vport_fc_functions = { + .show_host_node_name = 1, + .show_host_port_name = 1, + .show_host_supported_classes = 1, + .show_host_supported_fc4s = 1, + .show_host_active_fc4s = 1, + .show_host_maxframe_size = 1, + .show_host_serial_number = 1, + .show_host_manufacturer = 1, + .show_host_model = 1, + .show_host_model_description = 1, + .show_host_hardware_version = 1, + .show_host_driver_version = 1, + .show_host_firmware_version = 1, + .show_host_optionrom_version = 1, + + .show_host_port_id = 1, + .show_host_supported_speeds = 1, + .get_host_speed = fc_get_host_speed, + .show_host_speed = 1, + .show_host_port_type = 1, + .get_host_port_state = fc_get_host_port_state, + .show_host_port_state = 1, + .show_host_symbolic_name = 1, + + .dd_fcrport_size = sizeof(struct fc_rport_libfc_priv), + .show_rport_maxframe_size = 1, + .show_rport_supported_classes = 1, + + .show_host_fabric_name = 1, + .show_starget_node_name = 1, + .show_starget_port_name = 1, + .show_starget_port_id = 1, + .set_rport_dev_loss_tmo = fc_set_rport_loss_tmo, + .show_rport_dev_loss_tmo = 1, + .get_fc_host_stats = fc_get_host_stats, + .issue_fc_host_lip = fcoe_reset, + + .terminate_rport_io = fc_rport_terminate_io, + + .bsg_request = fc_lport_bsg_request, }; static struct scsi_host_template fcoe_shost_template = { @@ -137,20 +283,17 @@ static struct scsi_host_template fcoe_shost_template = { .change_queue_depth = fc_change_queue_depth, .change_queue_type = fc_change_queue_type, .this_id = -1, - .cmd_per_lun = 32, + .cmd_per_lun = 3, .can_queue = FCOE_MAX_OUTSTANDING_COMMANDS, .use_clustering = ENABLE_CLUSTERING, .sg_tablesize = SG_ALL, .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 + * fcoe_interface_setup() - Setup a FCoE interface + * @fcoe: The new FCoE interface + * @netdev: The net device that the fcoe interface is on * * Returns : 0 for success * Locking: must be called with the RTNL mutex held @@ -158,25 +301,37 @@ static int fcoe_fip_recv(struct sk_buff *skb, struct net_device *dev, static int fcoe_interface_setup(struct fcoe_interface *fcoe, struct net_device *netdev) { - struct fcoe_ctlr *fip = &fcoe->ctlr; + struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe); struct netdev_hw_addr *ha; + struct net_device *real_dev; u8 flogi_maddr[ETH_ALEN]; + const struct net_device_ops *ops; fcoe->netdev = netdev; + /* Let LLD initialize for FCoE */ + ops = netdev->netdev_ops; + if (ops->ndo_fcoe_enable) { + if (ops->ndo_fcoe_enable(netdev)) + FCOE_NETDEV_DBG(netdev, "Failed to enable FCoE" + " specific feature for LLD.\n"); + } + /* 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)) { + if (netdev->priv_flags & IFF_BONDING && netdev->flags & IFF_MASTER) { + FCOE_NETDEV_DBG(netdev, "Bonded interfaces not supported\n"); return -EOPNOTSUPP; } /* look for SAN MAC address, if multiple SAN MACs exist, only * use the first one for SPMA */ + real_dev = (netdev->priv_flags & IFF_802_1Q_VLAN) ? + vlan_dev_real_dev(netdev) : netdev; + fcoe->realdev = real_dev; rcu_read_lock(); - for_each_dev_addr(netdev, ha) { + for_each_dev_addr(real_dev, ha) { if ((ha->type == NETDEV_HW_ADDR_T_SAN) && - (is_valid_ether_addr(fip->ctl_src_addr))) { + (is_valid_ether_addr(ha->addr))) { memcpy(fip->ctl_src_addr, ha->addr, ETH_ALEN); fip->spma = 1; break; @@ -194,10 +349,14 @@ static int fcoe_interface_setup(struct fcoe_interface *fcoe, * for multiple unicast MACs. */ memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN); - dev_unicast_add(netdev, flogi_maddr); + dev_uc_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); + dev_uc_add(netdev, fip->ctl_src_addr); + if (fip->mode == FIP_MODE_VN2VN) { + dev_mc_add(netdev, FIP_ALL_VN2VN_MACS); + dev_mc_add(netdev, FIP_ALL_P2P_MACS); + } else + dev_mc_add(netdev, FIP_ALL_ENODE_MACS); /* * setup the receive function from ethernet driver @@ -216,52 +375,81 @@ static int fcoe_interface_setup(struct fcoe_interface *fcoe, 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 + * fcoe_interface_create() - Create a FCoE interface on a net device + * @netdev: The net device to create the FCoE interface on + * @fip_mode: The mode to use for FIP * * Returns: pointer to a struct fcoe_interface or NULL on error */ -static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev) +static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev, + enum fip_state fip_mode) { + struct fcoe_ctlr_device *ctlr_dev; + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; + int size; + int err; - fcoe = kzalloc(sizeof(*fcoe), GFP_KERNEL); - if (!fcoe) { - FCOE_NETDEV_DBG(netdev, "Could not allocate fcoe structure\n"); - return NULL; + if (!try_module_get(THIS_MODULE)) { + FCOE_NETDEV_DBG(netdev, + "Could not get a reference to the module\n"); + fcoe = ERR_PTR(-EBUSY); + goto out; } + size = sizeof(struct fcoe_ctlr) + sizeof(struct fcoe_interface); + ctlr_dev = fcoe_ctlr_device_add(&netdev->dev, &fcoe_sysfs_templ, + size); + if (!ctlr_dev) { + FCOE_DBG("Failed to add fcoe_ctlr_device\n"); + fcoe = ERR_PTR(-ENOMEM); + goto out_putmod; + } + + ctlr = fcoe_ctlr_device_priv(ctlr_dev); + ctlr->cdev = ctlr_dev; + fcoe = fcoe_ctlr_priv(ctlr); + 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_ctlr_init(ctlr, fip_mode); + ctlr->send = fcoe_fip_send; + ctlr->update_mac = fcoe_update_src_mac; + ctlr->get_src_addr = fcoe_get_src_mac; + + err = fcoe_interface_setup(fcoe, netdev); + if (err) { + fcoe_ctlr_destroy(ctlr); + fcoe_ctlr_device_delete(ctlr_dev); + dev_put(netdev); + fcoe = ERR_PTR(err); + goto out_putmod; + } - fcoe_interface_setup(fcoe, netdev); + goto out; +out_putmod: + module_put(THIS_MODULE); +out: return fcoe; } /** - * fcoe_interface_cleanup() - clean up netdev configurations - * @fcoe: + * fcoe_interface_remove() - remove FCoE interface from netdev + * @fcoe: The FCoE interface to be cleaned up * * Caller must be holding the RTNL mutex */ -void fcoe_interface_cleanup(struct fcoe_interface *fcoe) +static void fcoe_interface_remove(struct fcoe_interface *fcoe) { struct net_device *netdev = fcoe->netdev; - struct fcoe_ctlr *fip = &fcoe->ctlr; + struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe); u8 flogi_maddr[ETH_ALEN]; + const struct net_device_ops *ops; /* * Don't listen for Ethernet packets anymore. @@ -275,267 +463,433 @@ void fcoe_interface_cleanup(struct fcoe_interface *fcoe) /* 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); + dev_uc_del(netdev, flogi_maddr); if (fip->spma) - dev_unicast_delete(netdev, fip->ctl_src_addr); - dev_mc_delete(netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0); + dev_uc_del(netdev, fip->ctl_src_addr); + if (fip->mode == FIP_MODE_VN2VN) { + dev_mc_del(netdev, FIP_ALL_VN2VN_MACS); + dev_mc_del(netdev, FIP_ALL_P2P_MACS); + } else + dev_mc_del(netdev, FIP_ALL_ENODE_MACS); + + /* Tell the LLD we are done w/ FCoE */ + ops = netdev->netdev_ops; + if (ops->ndo_fcoe_disable) { + if (ops->ndo_fcoe_disable(netdev)) + FCOE_NETDEV_DBG(netdev, "Failed to disable FCoE" + " specific feature for LLD.\n"); + } + fcoe->removed = 1; } + /** - * fcoe_interface_release() - fcoe_port kref release function - * @kref: embedded reference count in an fcoe_interface struct + * fcoe_interface_cleanup() - Clean up a FCoE interface + * @fcoe: The FCoE interface to be cleaned up */ -static void fcoe_interface_release(struct kref *kref) +static void fcoe_interface_cleanup(struct fcoe_interface *fcoe) { - struct fcoe_interface *fcoe; - struct net_device *netdev; + struct net_device *netdev = fcoe->netdev; + struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe); - fcoe = container_of(kref, struct fcoe_interface, kref); - netdev = fcoe->netdev; + rtnl_lock(); + if (!fcoe->removed) + fcoe_interface_remove(fcoe); + rtnl_unlock(); + + /* Release the self-reference taken during fcoe_interface_create() */ /* tear-down the FCoE controller */ - fcoe_ctlr_destroy(&fcoe->ctlr); - kfree(fcoe); + fcoe_ctlr_destroy(fip); + scsi_host_put(fip->lp->host); dev_put(netdev); + module_put(THIS_MODULE); } /** - * 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 - * @dev: associated &net_device - * @ptype: the &packet_type structure which was used to register this handler. - * @orig_dev: original receive &net_device, in case @dev is a bond. + * fcoe_fip_recv() - Handler for received FIP frames + * @skb: The receive skb + * @netdev: The associated net device + * @ptype: The packet_type structure which was used to register this handler + * @orig_dev: The original net_device the the skb was received on. + * (in case dev is a bond) * * Returns: 0 for success */ -static int fcoe_fip_recv(struct sk_buff *skb, struct net_device *dev, +static int fcoe_fip_recv(struct sk_buff *skb, struct net_device *netdev, struct packet_type *ptype, struct net_device *orig_dev) { struct fcoe_interface *fcoe; + struct fcoe_ctlr *ctlr; fcoe = container_of(ptype, struct fcoe_interface, fip_packet_type); - fcoe_ctlr_recv(&fcoe->ctlr, skb); + ctlr = fcoe_to_ctlr(fcoe); + fcoe_ctlr_recv(ctlr, skb); return 0; } /** - * fcoe_fip_send() - send an Ethernet-encapsulated FIP frame. - * @fip: FCoE controller. - * @skb: FIP Packet. + * fcoe_port_send() - Send an Ethernet-encapsulated FIP/FCoE frame + * @port: The FCoE port + * @skb: The FIP/FCoE packet to be sent + */ +static void fcoe_port_send(struct fcoe_port *port, struct sk_buff *skb) +{ + if (port->fcoe_pending_queue.qlen) + fcoe_check_wait_queue(port->lport, skb); + else if (fcoe_start_io(skb)) + fcoe_check_wait_queue(port->lport, skb); +} + +/** + * fcoe_fip_send() - Send an Ethernet-encapsulated FIP frame + * @fip: The FCoE controller + * @skb: The FIP packet to be sent */ static void fcoe_fip_send(struct fcoe_ctlr *fip, struct sk_buff *skb) { skb->dev = fcoe_from_ctlr(fip)->netdev; - dev_queue_xmit(skb); + fcoe_port_send(lport_priv(fip->lp), skb); } /** - * fcoe_update_src_mac() - Update Ethernet MAC filters. - * @fip: FCoE controller. - * @old: Unicast MAC address to delete if the MAC is non-zero. - * @new: Unicast MAC address to add. + * fcoe_update_src_mac() - Update the Ethernet MAC filters + * @lport: The local port to update the source MAC on + * @addr: Unicast MAC address to add * * Remove any previously-set unicast MAC filter. * Add secondary FCoE MAC address filter for our OUI. */ -static void fcoe_update_src_mac(struct fcoe_ctlr *fip, u8 *old, u8 *new) +static void fcoe_update_src_mac(struct fc_lport *lport, u8 *addr) { - struct fcoe_interface *fcoe; + struct fcoe_port *port = lport_priv(lport); + struct fcoe_interface *fcoe = port->priv; - fcoe = fcoe_from_ctlr(fip); - rtnl_lock(); - if (!is_zero_ether_addr(old)) - dev_unicast_delete(fcoe->netdev, old); - dev_unicast_add(fcoe->netdev, new); - rtnl_unlock(); + if (!is_zero_ether_addr(port->data_src_addr)) + dev_uc_del(fcoe->netdev, port->data_src_addr); + if (!is_zero_ether_addr(addr)) + dev_uc_add(fcoe->netdev, addr); + memcpy(port->data_src_addr, addr, ETH_ALEN); +} + +/** + * fcoe_get_src_mac() - return the Ethernet source address for an lport + * @lport: libfc lport + */ +static u8 *fcoe_get_src_mac(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + + return port->data_src_addr; } /** - * fcoe_lport_config() - sets up the fc_lport - * @lp: ptr to the fc_lport + * fcoe_lport_config() - Set up a local port + * @lport: The local port to be setup * * Returns: 0 for success */ -static int fcoe_lport_config(struct fc_lport *lp) +static int fcoe_lport_config(struct fc_lport *lport) { - lp->link_up = 0; - lp->qfull = 0; - lp->max_retry_count = 3; - lp->max_rport_retry_count = 3; - lp->e_d_tov = 2 * 1000; /* FC-FS default */ - lp->r_a_tov = 2 * 2 * 1000; - lp->service_params = (FCP_SPPF_INIT_FCN | FCP_SPPF_RD_XRDY_DIS | - FCP_SPPF_RETRY | FCP_SPPF_CONF_COMPL); - - fc_lport_init_stats(lp); + lport->link_up = 0; + lport->qfull = 0; + lport->max_retry_count = 3; + lport->max_rport_retry_count = 3; + lport->e_d_tov = 2 * 1000; /* FC-FS default */ + lport->r_a_tov = 2 * 2 * 1000; + lport->service_params = (FCP_SPPF_INIT_FCN | FCP_SPPF_RD_XRDY_DIS | + FCP_SPPF_RETRY | FCP_SPPF_CONF_COMPL); + lport->does_npiv = 1; + + fc_lport_init_stats(lport); /* lport fc_lport related configuration */ - fc_lport_config(lp); + fc_lport_config(lport); /* offload related configuration */ - lp->crc_offload = 0; - lp->seq_offload = 0; - lp->lro_enabled = 0; - lp->lro_xid = 0; - lp->lso_max = 0; + lport->crc_offload = 0; + lport->seq_offload = 0; + lport->lro_enabled = 0; + lport->lro_xid = 0; + lport->lso_max = 0; return 0; } /** - * fcoe_queue_timer() - fcoe queue timer - * @lp: the fc_lport pointer - * - * Calls fcoe_check_wait_queue on timeout - * + * fcoe_netdev_features_change - Updates the lport's offload flags based + * on the LLD netdev's FCoE feature flags */ -static void fcoe_queue_timer(ulong lp) +static void fcoe_netdev_features_change(struct fc_lport *lport, + struct net_device *netdev) { - fcoe_check_wait_queue((struct fc_lport *)lp, NULL); + mutex_lock(&lport->lp_mutex); + + if (netdev->features & NETIF_F_SG) + lport->sg_supp = 1; + else + lport->sg_supp = 0; + + if (netdev->features & NETIF_F_FCOE_CRC) { + lport->crc_offload = 1; + FCOE_NETDEV_DBG(netdev, "Supports FCCRC offload\n"); + } else { + lport->crc_offload = 0; + } + + if (netdev->features & NETIF_F_FSO) { + lport->seq_offload = 1; + lport->lso_max = netdev->gso_max_size; + FCOE_NETDEV_DBG(netdev, "Supports LSO for max len 0x%x\n", + lport->lso_max); + } else { + lport->seq_offload = 0; + lport->lso_max = 0; + } + + if (netdev->fcoe_ddp_xid) { + lport->lro_enabled = 1; + lport->lro_xid = netdev->fcoe_ddp_xid; + FCOE_NETDEV_DBG(netdev, "Supports LRO for max xid 0x%x\n", + lport->lro_xid); + } else { + lport->lro_enabled = 0; + lport->lro_xid = 0; + } + + mutex_unlock(&lport->lp_mutex); } /** - * fcoe_netdev_config() - Set up netdev for SW FCoE - * @lp : ptr to the fc_lport - * @netdev : ptr to the associated netdevice struct + * fcoe_netdev_config() - Set up net devive for SW FCoE + * @lport: The local port that is associated with the net device + * @netdev: The associated net device * - * Must be called after fcoe_lport_config() as it will use lport mutex + * Must be called after fcoe_lport_config() as it will use local port mutex * - * Returns : 0 for success + * Returns: 0 for success */ -static int fcoe_netdev_config(struct fc_lport *lp, struct net_device *netdev) +static int fcoe_netdev_config(struct fc_lport *lport, struct net_device *netdev) { u32 mfs; u64 wwnn, wwpn; struct fcoe_interface *fcoe; + struct fcoe_ctlr *ctlr; struct fcoe_port *port; /* Setup lport private data to point to fcoe softc */ - port = lport_priv(lp); - fcoe = port->fcoe; + port = lport_priv(lport); + fcoe = port->priv; + ctlr = fcoe_to_ctlr(fcoe); /* * Determine max frame size based on underlying device and optional * user-configured limit. If the MFS is too low, fcoe_link_ok() * will return 0, so do this first. */ - mfs = netdev->mtu - (sizeof(struct fcoe_hdr) + - sizeof(struct fcoe_crc_eof)); - if (fc_set_mfs(lp, mfs)) + mfs = netdev->mtu; + if (netdev->features & NETIF_F_FCOE_MTU) { + mfs = FCOE_MTU; + FCOE_NETDEV_DBG(netdev, "Supports FCOE_MTU of %d bytes\n", mfs); + } + mfs -= (sizeof(struct fcoe_hdr) + sizeof(struct fcoe_crc_eof)); + if (fc_set_mfs(lport, mfs)) return -EINVAL; /* offload features support */ - if (netdev->features & NETIF_F_SG) - lp->sg_supp = 1; + fcoe_netdev_features_change(lport, netdev); - if (netdev->features & NETIF_F_FCOE_CRC) { - lp->crc_offload = 1; - FCOE_NETDEV_DBG(netdev, "Supports FCCRC offload\n"); - } - if (netdev->features & NETIF_F_FSO) { - lp->seq_offload = 1; - lp->lso_max = netdev->gso_max_size; - FCOE_NETDEV_DBG(netdev, "Supports LSO for max len 0x%x\n", - lp->lso_max); - } - if (netdev->fcoe_ddp_xid) { - lp->lro_enabled = 1; - lp->lro_xid = netdev->fcoe_ddp_xid; - FCOE_NETDEV_DBG(netdev, "Supports LRO for max xid 0x%x\n", - lp->lro_xid); - } skb_queue_head_init(&port->fcoe_pending_queue); port->fcoe_pending_queue_active = 0; - setup_timer(&port->timer, fcoe_queue_timer, (unsigned long)lp); - - 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); + setup_timer(&port->timer, fcoe_queue_timer, (unsigned long)lport); + + fcoe_link_speed_update(lport); + + if (!lport->vport) { + if (fcoe_get_wwn(netdev, &wwnn, NETDEV_FCOE_WWNN)) + wwnn = fcoe_wwn_from_mac(ctlr->ctl_src_addr, 1, 0); + fc_set_wwnn(lport, wwnn); + if (fcoe_get_wwn(netdev, &wwpn, NETDEV_FCOE_WWPN)) + wwpn = fcoe_wwn_from_mac(ctlr->ctl_src_addr, + 2, 0); + fc_set_wwpn(lport, wwpn); + } return 0; } /** - * fcoe_shost_config() - Sets up fc_lport->host - * @lp : ptr to the fc_lport - * @shost : ptr to the associated scsi host - * @dev : device associated to scsi host + * fcoe_shost_config() - Set up the SCSI host associated with a local port + * @lport: The local port + * @dev: The device associated with the SCSI host * * Must be called after fcoe_lport_config() and fcoe_netdev_config() * - * Returns : 0 for success + * Returns: 0 for success */ -static int fcoe_shost_config(struct fc_lport *lp, struct Scsi_Host *shost, - struct device *dev) +static int fcoe_shost_config(struct fc_lport *lport, struct device *dev) { int rc = 0; /* lport scsi host config */ - lp->host = shost; + lport->host->max_lun = FCOE_MAX_LUN; + lport->host->max_id = FCOE_MAX_FCP_TARGET; + lport->host->max_channel = 0; + lport->host->max_cmd_len = FCOE_MAX_CMD_LEN; - lp->host->max_lun = FCOE_MAX_LUN; - lp->host->max_id = FCOE_MAX_FCP_TARGET; - lp->host->max_channel = 0; - lp->host->transportt = scsi_transport_fcoe_sw; + if (lport->vport) + lport->host->transportt = fcoe_vport_scsi_transport; + else + lport->host->transportt = fcoe_nport_scsi_transport; /* add the new host to the SCSI-ml */ - rc = scsi_add_host(lp->host, dev); + rc = scsi_add_host(lport->host, dev); if (rc) { - FCOE_NETDEV_DBG(fcoe_netdev(lp), "fcoe_shost_config: " + FCOE_NETDEV_DBG(fcoe_netdev(lport), "fcoe_shost_config: " "error on scsi_add_host\n"); return rc; } - sprintf(fc_host_symbolic_name(lp->host), "%s v%s over %s", - FCOE_NAME, FCOE_VERSION, - fcoe_netdev(lp)->name); + + if (!lport->vport) + fc_host_max_npiv_vports(lport->host) = USHRT_MAX; + + snprintf(fc_host_symbolic_name(lport->host), FC_SYMBOLIC_NAME_SIZE, + "%s v%s over %s", FCOE_NAME, FCOE_VERSION, + fcoe_netdev(lport)->name); return 0; } -/* - * fcoe_oem_match() - match for read types IO - * @fp: the fc_frame for new IO. + +/** + * fcoe_fdmi_info() - Get FDMI related info from net devive for SW FCoE + * @lport: The local port that is associated with the net device + * @netdev: The associated net device + * + * Must be called after fcoe_shost_config() as it will use local port mutex * - * Returns : true for read types IO, otherwise returns false. */ -bool fcoe_oem_match(struct fc_frame *fp) +static void fcoe_fdmi_info(struct fc_lport *lport, struct net_device *netdev) { - return fc_fcp_is_read(fr_fsp(fp)) && - (fr_fsp(fp)->data_len > fcoe_ddp_min); + struct fcoe_interface *fcoe; + struct fcoe_port *port; + struct net_device *realdev; + int rc; + + port = lport_priv(lport); + fcoe = port->priv; + realdev = fcoe->realdev; + + if (!realdev) + return; + + /* No FDMI state m/c for NPIV ports */ + if (lport->vport) + return; + + if (realdev->netdev_ops->ndo_fcoe_get_hbainfo) { + struct netdev_fcoe_hbainfo *fdmi; + fdmi = kzalloc(sizeof(*fdmi), GFP_KERNEL); + if (!fdmi) + return; + + rc = realdev->netdev_ops->ndo_fcoe_get_hbainfo(realdev, + fdmi); + if (rc) { + printk(KERN_INFO "fcoe: Failed to retrieve FDMI " + "information from netdev.\n"); + return; + } + + snprintf(fc_host_serial_number(lport->host), + FC_SERIAL_NUMBER_SIZE, + "%s", + fdmi->serial_number); + snprintf(fc_host_manufacturer(lport->host), + FC_SERIAL_NUMBER_SIZE, + "%s", + fdmi->manufacturer); + snprintf(fc_host_model(lport->host), + FC_SYMBOLIC_NAME_SIZE, + "%s", + fdmi->model); + snprintf(fc_host_model_description(lport->host), + FC_SYMBOLIC_NAME_SIZE, + "%s", + fdmi->model_description); + snprintf(fc_host_hardware_version(lport->host), + FC_VERSION_STRING_SIZE, + "%s", + fdmi->hardware_version); + snprintf(fc_host_driver_version(lport->host), + FC_VERSION_STRING_SIZE, + "%s", + fdmi->driver_version); + snprintf(fc_host_optionrom_version(lport->host), + FC_VERSION_STRING_SIZE, + "%s", + fdmi->optionrom_version); + snprintf(fc_host_firmware_version(lport->host), + FC_VERSION_STRING_SIZE, + "%s", + fdmi->firmware_version); + + /* Enable FDMI lport states */ + lport->fdmi_enabled = 1; + kfree(fdmi); + } else { + lport->fdmi_enabled = 0; + printk(KERN_INFO "fcoe: No FDMI support.\n"); + } } /** - * fcoe_em_config() - allocates em for this lport - * @lp: the fcoe that em is to allocated for + * fcoe_oem_match() - The match routine for the offloaded exchange manager + * @fp: The I/O frame * - * Returns : 0 on success + * This routine will be associated with an exchange manager (EM). When + * the libfc exchange handling code is looking for an EM to use it will + * call this routine and pass it the frame that it wishes to send. This + * routine will return True if the associated EM is to be used and False + * if the echange code should continue looking for an EM. + * + * The offload EM that this routine is associated with will handle any + * packets that are for SCSI read requests. + * + * This has been enhanced to work when FCoE stack is operating in target + * mode. + * + * Returns: True for read types I/O, otherwise returns false. */ -static inline int fcoe_em_config(struct fc_lport *lp) +static bool fcoe_oem_match(struct fc_frame *fp) { - struct fcoe_port *port = lport_priv(lp); - struct fcoe_interface *fcoe = port->fcoe; + struct fc_frame_header *fh = fc_frame_header_get(fp); + struct fcp_cmnd *fcp; + + if (fc_fcp_is_read(fr_fsp(fp)) && + (fr_fsp(fp)->data_len > fcoe_ddp_min)) + return true; + else if ((fr_fsp(fp) == NULL) && + (fh->fh_r_ctl == FC_RCTL_DD_UNSOL_CMD) && + (ntohs(fh->fh_rx_id) == FC_XID_UNKNOWN)) { + fcp = fc_frame_payload_get(fp, sizeof(*fcp)); + if ((fcp->fc_flags & FCP_CFL_WRDATA) && + (ntohl(fcp->fc_dl) > fcoe_ddp_min)) + return true; + } + return false; +} + +/** + * fcoe_em_config() - Allocate and configure an exchange manager + * @lport: The local port that the new EM will be associated with + * + * Returns: 0 on success + */ +static inline int fcoe_em_config(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + struct fcoe_interface *fcoe = port->priv; struct fcoe_interface *oldfcoe = NULL; struct net_device *old_real_dev, *cur_real_dev; u16 min_xid = FCOE_MIN_XID; @@ -545,8 +899,9 @@ static inline int fcoe_em_config(struct fc_lport *lp) * Check if need to allocate an em instance for * offload exchange ids to be shared across all VN_PORTs/lport. */ - if (!lp->lro_enabled || !lp->lro_xid || (lp->lro_xid >= max_xid)) { - lp->lro_xid = 0; + if (!lport->lro_enabled || !lport->lro_xid || + (lport->lro_xid >= max_xid)) { + lport->lro_xid = 0; goto skip_oem; } @@ -572,16 +927,16 @@ static inline int fcoe_em_config(struct fc_lport *lp) } if (fcoe->oem) { - if (!fc_exch_mgr_add(lp, fcoe->oem, fcoe_oem_match)) { + if (!fc_exch_mgr_add(lport, fcoe->oem, fcoe_oem_match)) { printk(KERN_ERR "fcoe_em_config: failed to add " "offload em:%p on interface:%s\n", fcoe->oem, fcoe->netdev->name); return -ENOMEM; } } else { - fcoe->oem = fc_exch_mgr_alloc(lp, FC_CLASS_3, - FCOE_MIN_XID, lp->lro_xid, - fcoe_oem_match); + fcoe->oem = fc_exch_mgr_alloc(lport, FC_CLASS_3, + FCOE_MIN_XID, lport->lro_xid, + fcoe_oem_match); if (!fcoe->oem) { printk(KERN_ERR "fcoe_em_config: failed to allocate " "em for offload exches on interface:%s\n", @@ -593,10 +948,10 @@ static inline int fcoe_em_config(struct fc_lport *lp) /* * Exclude offload EM xid range from next EM xid range. */ - min_xid += lp->lro_xid + 1; + min_xid += lport->lro_xid + 1; skip_oem: - if (!fc_exch_mgr_alloc(lp, FC_CLASS_3, min_xid, max_xid, NULL)) { + if (!fc_exch_mgr_alloc(lport, FC_CLASS_3, min_xid, max_xid, NULL)) { printk(KERN_ERR "fcoe_em_config: failed to " "allocate em on interface %s\n", fcoe->netdev->name); return -ENOMEM; @@ -606,13 +961,14 @@ skip_oem: } /** - * fcoe_if_destroy() - FCoE software HBA tear-down function - * @lport: fc_lport to destroy + * fcoe_if_destroy() - Tear down a SW FCoE instance + * @lport: The local port to be destroyed + * */ static void fcoe_if_destroy(struct fc_lport *lport) { struct fcoe_port *port = lport_priv(lport); - struct fcoe_interface *fcoe = port->fcoe; + struct fcoe_interface *fcoe = port->priv; struct net_device *netdev = fcoe->netdev; FCOE_NETDEV_DBG(netdev, "Destroying interface\n"); @@ -622,7 +978,6 @@ static void fcoe_if_destroy(struct fc_lport *lport) /* Cleanup the fc_lport */ fc_lport_destroy(lport); - fc_fcp_destroy(lport); /* Stop the transmit retry timer */ del_timer_sync(&port->timer); @@ -630,8 +985,14 @@ static void fcoe_if_destroy(struct fc_lport *lport) /* Free existing transmit skbs */ fcoe_clean_pending_queue(lport); - /* receives may not be stopped until after this */ - fcoe_interface_put(fcoe); + rtnl_lock(); + if (!is_zero_ether_addr(port->data_src_addr)) + dev_uc_del(netdev, port->data_src_addr); + if (lport->vport) + synchronize_net(); + else + fcoe_interface_remove(fcoe); + rtnl_unlock(); /* Free queued packets for the per-CPU receive threads */ fcoe_percpu_clean(lport); @@ -640,92 +1001,135 @@ static void fcoe_if_destroy(struct fc_lport *lport) fc_remove_host(lport->host); scsi_remove_host(lport->host); + /* Destroy lport scsi_priv */ + fc_fcp_destroy(lport); + /* There are no more rports or I/O, free the EM */ fc_exch_mgr_free(lport); /* Free memory used by statistical counters */ fc_lport_free_stats(lport); - /* Release the Scsi_Host */ - scsi_host_put(lport->host); + /* + * Release the Scsi_Host for vport but hold on to + * master lport until it fcoe interface fully cleaned-up. + */ + if (lport->vport) + scsi_host_put(lport->host); } -/* - * fcoe_ddp_setup - calls LLD's ddp_setup through net_device - * @lp: the corresponding fc_lport - * @xid: the exchange id for this ddp transfer - * @sgl: the scatterlist describing this transfer - * @sgc: number of sg items +/** + * fcoe_ddp_setup() - Call a LLD's ddp_setup through the net device + * @lport: The local port to setup DDP for + * @xid: The exchange ID for this DDP transfer + * @sgl: The scatterlist describing this transfer + * @sgc: The number of sg items * - * Returns : 0 no ddp + * Returns: 0 if the DDP context was not configured */ -static int fcoe_ddp_setup(struct fc_lport *lp, u16 xid, - struct scatterlist *sgl, unsigned int sgc) +static int fcoe_ddp_setup(struct fc_lport *lport, u16 xid, + struct scatterlist *sgl, unsigned int sgc) { - struct net_device *n = fcoe_netdev(lp); + struct net_device *netdev = fcoe_netdev(lport); - if (n->netdev_ops && n->netdev_ops->ndo_fcoe_ddp_setup) - return n->netdev_ops->ndo_fcoe_ddp_setup(n, xid, sgl, sgc); + if (netdev->netdev_ops->ndo_fcoe_ddp_setup) + return netdev->netdev_ops->ndo_fcoe_ddp_setup(netdev, + xid, sgl, + sgc); return 0; } -/* - * fcoe_ddp_done - calls LLD's ddp_done through net_device - * @lp: the corresponding fc_lport - * @xid: the exchange id for this ddp transfer +/** + * fcoe_ddp_target() - Call a LLD's ddp_target through the net device + * @lport: The local port to setup DDP for + * @xid: The exchange ID for this DDP transfer + * @sgl: The scatterlist describing this transfer + * @sgc: The number of sg items * - * Returns : the length of data that have been completed by ddp + * Returns: 0 if the DDP context was not configured */ -static int fcoe_ddp_done(struct fc_lport *lp, u16 xid) +static int fcoe_ddp_target(struct fc_lport *lport, u16 xid, + struct scatterlist *sgl, unsigned int sgc) { - struct net_device *n = fcoe_netdev(lp); + struct net_device *netdev = fcoe_netdev(lport); + + if (netdev->netdev_ops->ndo_fcoe_ddp_target) + return netdev->netdev_ops->ndo_fcoe_ddp_target(netdev, xid, + sgl, sgc); - if (n->netdev_ops && n->netdev_ops->ndo_fcoe_ddp_done) - return n->netdev_ops->ndo_fcoe_ddp_done(n, xid); return 0; } -static struct libfc_function_template fcoe_libfc_fcn_templ = { - .frame_send = fcoe_xmit, - .ddp_setup = fcoe_ddp_setup, - .ddp_done = fcoe_ddp_done, -}; /** - * 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 + * fcoe_ddp_done() - Call a LLD's ddp_done through the net device + * @lport: The local port to complete DDP on + * @xid: The exchange ID for this DDP transfer * - * Creates fc_lport struct and scsi_host for lport, configures lport. + * Returns: the length of data that have been completed by DDP + */ +static int fcoe_ddp_done(struct fc_lport *lport, u16 xid) +{ + struct net_device *netdev = fcoe_netdev(lport); + + if (netdev->netdev_ops->ndo_fcoe_ddp_done) + return netdev->netdev_ops->ndo_fcoe_ddp_done(netdev, xid); + return 0; +} + +/** + * fcoe_if_create() - Create a FCoE instance on an interface + * @fcoe: The FCoE interface to create a local port on + * @parent: The device pointer to be the parent in sysfs for the SCSI host + * @npiv: Indicates if the port is a vport or not + * + * Creates a fc_lport instance and a Scsi_Host instance and configure them. * - * Returns : The allocated fc_lport or an error pointer + * Returns: The allocated fc_lport or an error pointer */ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe, - struct device *parent) + struct device *parent, int npiv) { - int rc; - struct fc_lport *lport = NULL; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); + struct net_device *netdev = fcoe->netdev; + struct fc_lport *lport, *n_port; struct fcoe_port *port; struct Scsi_Host *shost; - struct net_device *netdev = fcoe->netdev; + int rc; + /* + * parent is only a vport if npiv is 1, + * but we'll only use vport in that case so go ahead and set it + */ + struct fc_vport *vport = dev_to_vport(parent); FCOE_NETDEV_DBG(netdev, "Create Interface\n"); - shost = libfc_host_alloc(&fcoe_shost_template, - sizeof(struct fcoe_port)); - if (!shost) { + if (!npiv) + lport = libfc_host_alloc(&fcoe_shost_template, sizeof(*port)); + else + lport = libfc_vport_create(vport, sizeof(*port)); + + if (!lport) { FCOE_NETDEV_DBG(netdev, "Could not allocate host structure\n"); rc = -ENOMEM; goto out; } - lport = shost_priv(shost); port = lport_priv(lport); port->lport = lport; - port->fcoe = fcoe; + port->priv = fcoe; + port->get_netdev = fcoe_netdev; + port->max_queue_depth = FCOE_MAX_QUEUE_DEPTH; + port->min_queue_depth = FCOE_MIN_QUEUE_DEPTH; INIT_WORK(&port->destroy_work, fcoe_destroy_work); - /* configure fc_lport, e.g., em */ + /* + * Need to add the lport to the hostlist + * so we catch NETDEV_CHANGE events. + */ + fcoe_hostlist_add(lport); + + /* configure a fc_lport including the exchange manager */ rc = fcoe_lport_config(lport); if (rc) { FCOE_NETDEV_DBG(netdev, "Could not configure lport for the " @@ -733,6 +1137,14 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe, goto out_host_put; } + if (npiv) { + FCOE_NETDEV_DBG(netdev, "Setting vport names, " + "%16.16llx %16.16llx\n", + vport->node_name, vport->port_name); + fc_set_wwnn(lport, vport->node_name); + fc_set_wwpn(lport, vport->port_name); + } + /* configure lport network properties */ rc = fcoe_netdev_config(lport, netdev); if (rc) { @@ -742,7 +1154,7 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe, } /* configure lport scsi host properties */ - rc = fcoe_shost_config(lport, shost, parent); + rc = fcoe_shost_config(lport, parent); if (rc) { FCOE_NETDEV_DBG(netdev, "Could not configure shost for the " "interface\n"); @@ -750,53 +1162,66 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe, } /* Initialize the library */ - rc = fcoe_libfc_config(lport, &fcoe_libfc_fcn_templ); + rc = fcoe_libfc_config(lport, ctlr, &fcoe_libfc_fcn_templ, 1); if (rc) { FCOE_NETDEV_DBG(netdev, "Could not configure libfc for the " "interface\n"); goto out_lp_destroy; } + /* Initialized FDMI information */ + fcoe_fdmi_info(lport, netdev); + /* * fcoe_em_alloc() and fcoe_hostlist_add() both - * need to be atomic with respect to other changes to the hostlist - * since fcoe_em_alloc() looks for an existing EM + * 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. + * This is currently handled through the fcoe_config_mutex + * begin held. */ + if (!npiv) + /* lport exch manager allocation */ + rc = fcoe_em_config(lport); + else { + shost = vport_to_shost(vport); + n_port = shost_priv(shost); + rc = fc_exch_mgr_list_clone(n_port, lport); + } - /* lport exch manager allocation */ - rc = fcoe_em_config(lport); if (rc) { - FCOE_NETDEV_DBG(netdev, "Could not configure the EM for the " - "interface\n"); + FCOE_NETDEV_DBG(netdev, "Could not configure the EM\n"); goto out_lp_destroy; } - fcoe_interface_get(fcoe); return lport; out_lp_destroy: fc_exch_mgr_free(lport); out_host_put: + fcoe_hostlist_del(lport); scsi_host_put(lport->host); out: return ERR_PTR(rc); } /** - * fcoe_if_init() - attach to scsi transport + * fcoe_if_init() - Initialization routine for fcoe.ko * - * Returns : 0 on success + * Attaches the SW FCoE transport to the FC transport + * + * Returns: 0 on success */ static int __init fcoe_if_init(void) { /* attach to scsi transport */ - scsi_transport_fcoe_sw = - fc_attach_transport(&fcoe_transport_function); + fcoe_nport_scsi_transport = + fc_attach_transport(&fcoe_nport_fc_functions); + fcoe_vport_scsi_transport = + fc_attach_transport(&fcoe_vport_fc_functions); - if (!scsi_transport_fcoe_sw) { + if (!fcoe_nport_scsi_transport) { printk(KERN_ERR "fcoe: Failed to attach to the FC transport\n"); return -ENODEV; } @@ -805,20 +1230,24 @@ static int __init fcoe_if_init(void) } /** - * fcoe_if_exit() - detach from scsi transport + * fcoe_if_exit() - Tear down fcoe.ko + * + * Detaches the SW FCoE transport from the FC transport * - * Returns : 0 on success + * Returns: 0 on success */ -int __exit fcoe_if_exit(void) +static int __exit fcoe_if_exit(void) { - fc_release_transport(scsi_transport_fcoe_sw); - scsi_transport_fcoe_sw = NULL; + fc_release_transport(fcoe_nport_scsi_transport); + fc_release_transport(fcoe_vport_scsi_transport); + fcoe_nport_scsi_transport = NULL; + fcoe_vport_scsi_transport = NULL; return 0; } /** - * fcoe_percpu_thread_create() - Create a receive thread for an online cpu - * @cpu: cpu index for the online cpu + * fcoe_percpu_thread_create() - Create a receive thread for an online CPU + * @cpu: The CPU index of the CPU to create a receive thread for */ static void fcoe_percpu_thread_create(unsigned int cpu) { @@ -827,8 +1256,9 @@ static void fcoe_percpu_thread_create(unsigned int cpu) p = &per_cpu(fcoe_percpu, cpu); - thread = kthread_create(fcoe_percpu_receive_thread, - (void *)p, "fcoethread/%d", cpu); + thread = kthread_create_on_node(fcoe_percpu_receive_thread, + (void *)p, cpu_to_node(cpu), + "fcoethread/%d", cpu); if (likely(!IS_ERR(thread))) { kthread_bind(thread, cpu); @@ -841,8 +1271,8 @@ static void fcoe_percpu_thread_create(unsigned int cpu) } /** - * fcoe_percpu_thread_destroy() - removes the rx thread for the given cpu - * @cpu: cpu index the rx thread is to be removed + * fcoe_percpu_thread_destroy() - Remove the receive thread of a CPU + * @cpu: The CPU index of the CPU whose receive thread is to be destroyed * * Destroys a per-CPU Rx thread. Any pending skbs are moved to the * current CPU's Rx thread. If the thread being destroyed is bound to @@ -856,7 +1286,7 @@ static void fcoe_percpu_thread_destroy(unsigned int cpu) struct sk_buff *skb; #ifdef CONFIG_SMP struct fcoe_percpu_s *p0; - unsigned targ_cpu = smp_processor_id(); + unsigned targ_cpu = get_cpu(); #endif /* CONFIG_SMP */ FCOE_DBG("Destroying receive thread for CPU %d\n", cpu); @@ -890,7 +1320,7 @@ static void fcoe_percpu_thread_destroy(unsigned int cpu) } else { /* * The targeted CPU is not initialized and cannot accept - * new skbs. Unlock the targeted CPU and drop the skbs + * new skbs. Unlock the targeted CPU and drop the skbs * on the CPU that is going offline. */ while ((skb = __skb_dequeue(&p->fcoe_rx_list)) != NULL) @@ -912,6 +1342,7 @@ static void fcoe_percpu_thread_destroy(unsigned int cpu) kfree_skb(skb); spin_unlock_bh(&p->fcoe_rx_list.lock); } + put_cpu(); #else /* * This a non-SMP scenario where the singular Rx thread is @@ -931,12 +1362,12 @@ static void fcoe_percpu_thread_destroy(unsigned int cpu) } /** - * fcoe_cpu_callback() - fcoe cpu hotplug event callback - * @nfb: callback data block - * @action: event triggering the callback - * @hcpu: index for the cpu of this event + * fcoe_cpu_callback() - Handler for CPU hotplug events + * @nfb: The callback data block + * @action: The event triggering the callback + * @hcpu: The index of the CPU that the event is for * - * This creates or destroys per cpu data for fcoe + * This creates or destroys per-CPU data for fcoe * * Returns NOTIFY_OK always. */ @@ -962,49 +1393,78 @@ static int fcoe_cpu_callback(struct notifier_block *nfb, return NOTIFY_OK; } -static struct notifier_block fcoe_cpu_notifier = { - .notifier_call = fcoe_cpu_callback, -}; +/** + * fcoe_select_cpu() - Selects CPU to handle post-processing of incoming + * command. + * + * This routine selects next CPU based on cpumask to distribute + * incoming requests in round robin. + * + * Returns: int CPU number + */ +static inline unsigned int fcoe_select_cpu(void) +{ + static unsigned int selected_cpu; + + selected_cpu = cpumask_next(selected_cpu, cpu_online_mask); + if (selected_cpu >= nr_cpu_ids) + selected_cpu = cpumask_first(cpu_online_mask); + + return selected_cpu; +} /** - * fcoe_rcv() - this is the fcoe receive function called by NET_RX_SOFTIRQ - * @skb: the receive skb - * @dev: associated net device - * @ptype: context - * @olddev: last device + * fcoe_rcv() - Receive packets from a net device + * @skb: The received packet + * @netdev: The net device that the packet was received on + * @ptype: The packet type context + * @olddev: The last device net device * - * this function will receive the packet and build fc frame and pass it up + * This routine is called by NET_RX_SOFTIRQ. It receives a packet, builds a + * FC frame and passes the frame to libfc. * * Returns: 0 for success */ -int fcoe_rcv(struct sk_buff *skb, struct net_device *dev, +static int fcoe_rcv(struct sk_buff *skb, struct net_device *netdev, struct packet_type *ptype, struct net_device *olddev) { - struct fc_lport *lp; + struct fc_lport *lport; struct fcoe_rcv_info *fr; + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; struct fc_frame_header *fh; struct fcoe_percpu_s *fps; + struct ethhdr *eh; unsigned int cpu; fcoe = container_of(ptype, struct fcoe_interface, fcoe_packet_type); - lp = fcoe->ctlr.lp; - if (unlikely(lp == NULL)) { - FCOE_NETDEV_DBG(dev, "Cannot find hba structure"); + ctlr = fcoe_to_ctlr(fcoe); + lport = ctlr->lp; + if (unlikely(!lport)) { + FCOE_NETDEV_DBG(netdev, "Cannot find hba structure\n"); goto err2; } - if (!lp->link_up) + if (!lport->link_up) goto err2; - FCOE_NETDEV_DBG(dev, "skb_info: len:%d data_len:%d head:%p " - "data:%p tail:%p end:%p sum:%d dev:%s", + FCOE_NETDEV_DBG(netdev, + "skb_info: len:%d data_len:%d head:%p data:%p tail:%p end:%p sum:%d dev:%s\n", skb->len, skb->data_len, skb->head, skb->data, skb_tail_pointer(skb), skb_end_pointer(skb), skb->csum, skb->dev ? skb->dev->name : "<NULL>"); - /* check for FCOE packet type */ - if (unlikely(eth_hdr(skb)->h_proto != htons(ETH_P_FCOE))) { - FCOE_NETDEV_DBG(dev, "Wrong FC type frame"); + + skb = skb_share_check(skb, GFP_ATOMIC); + + if (skb == NULL) + return NET_RX_DROP; + + eh = eth_hdr(skb); + + if (is_fip_mode(ctlr) && + !ether_addr_equal(eh->h_source, ctlr->dest_addr)) { + FCOE_NETDEV_DBG(netdev, "wrong source mac address:%pM\n", + eh->h_source); goto err; } @@ -1013,45 +1473,58 @@ int fcoe_rcv(struct sk_buff *skb, struct net_device *dev, * and FC headers are pulled into the linear data area. */ if (unlikely((skb->len < FCOE_MIN_FRAME) || - !pskb_may_pull(skb, FCOE_HEADER_LEN))) + !pskb_may_pull(skb, FCOE_HEADER_LEN))) goto err; skb_set_transport_header(skb, sizeof(struct fcoe_hdr)); fh = (struct fc_frame_header *) skb_transport_header(skb); + if (ntoh24(&eh->h_dest[3]) != ntoh24(fh->fh_d_id)) { + FCOE_NETDEV_DBG(netdev, "FC frame d_id mismatch with MAC:%pM\n", + eh->h_dest); + goto err; + } + fr = fcoe_dev_from_skb(skb); - fr->fr_dev = lp; - fr->ptype = ptype; + fr->fr_dev = lport; /* * In case the incoming frame's exchange is originated from * the initiator, then received frame's exchange id is ANDed * with fc_cpu_mask bits to get the same cpu on which exchange - * was originated, otherwise just use the current cpu. + * was originated, otherwise select cpu using rx exchange id + * or fcoe_select_cpu(). */ if (ntoh24(fh->fh_f_ctl) & FC_FC_EX_CTX) cpu = ntohs(fh->fh_ox_id) & fc_cpu_mask; - else - cpu = smp_processor_id(); + else { + if (ntohs(fh->fh_rx_id) == FC_XID_UNKNOWN) + cpu = fcoe_select_cpu(); + else + cpu = ntohs(fh->fh_rx_id) & fc_cpu_mask; + } + + if (cpu >= nr_cpu_ids) + goto err; fps = &per_cpu(fcoe_percpu, cpu); - spin_lock_bh(&fps->fcoe_rx_list.lock); + spin_lock(&fps->fcoe_rx_list.lock); if (unlikely(!fps->thread)) { /* * The targeted CPU is not ready, let's target * the first CPU now. For non-SMP systems this * will check the same CPU twice. */ - FCOE_NETDEV_DBG(dev, "CPU is online, but no receive thread " + FCOE_NETDEV_DBG(netdev, "CPU is online, but no receive thread " "ready for incoming skb- using first online " "CPU.\n"); - spin_unlock_bh(&fps->fcoe_rx_list.lock); - cpu = first_cpu(cpu_online_map); + spin_unlock(&fps->fcoe_rx_list.lock); + cpu = cpumask_first(cpu_online_mask); fps = &per_cpu(fcoe_percpu, cpu); - spin_lock_bh(&fps->fcoe_rx_list.lock); + spin_lock(&fps->fcoe_rx_list.lock); if (!fps->thread) { - spin_unlock_bh(&fps->fcoe_rx_list.lock); + spin_unlock(&fps->fcoe_rx_list.lock); goto err; } } @@ -1061,136 +1534,69 @@ int fcoe_rcv(struct sk_buff *skb, struct net_device *dev, * this skb. We also have this receive thread locked, * so we're free to queue skbs into it's queue. */ + + /* + * Note: We used to have a set of conditions under which we would + * call fcoe_recv_frame directly, rather than queuing to the rx list + * as it could save a few cycles, but doing so is prohibited, as + * fcoe_recv_frame has several paths that may sleep, which is forbidden + * in softirq context. + */ __skb_queue_tail(&fps->fcoe_rx_list, skb); - if (fps->fcoe_rx_list.qlen == 1) + if (fps->thread->state == TASK_INTERRUPTIBLE) wake_up_process(fps->thread); + spin_unlock(&fps->fcoe_rx_list.lock); - spin_unlock_bh(&fps->fcoe_rx_list.lock); - - return 0; + return NET_RX_SUCCESS; err: - fc_lport_get_stats(lp)->ErrorFrames++; - + per_cpu_ptr(lport->stats, get_cpu())->ErrorFrames++; + put_cpu(); err2: kfree_skb(skb); - return -1; -} - -/** - * fcoe_start_io() - pass to netdev to start xmit for fcoe - * @skb: the skb to be xmitted - * - * Returns: 0 for success - */ -static inline int fcoe_start_io(struct sk_buff *skb) -{ - int rc; - - skb_get(skb); - rc = dev_queue_xmit(skb); - if (rc != 0) - return rc; - kfree_skb(skb); - return 0; + return NET_RX_DROP; } /** - * fcoe_get_paged_crc_eof() - in case we need to alloc a page for crc_eof - * @skb: the skb to be xmitted - * @tlen: total len + * fcoe_alloc_paged_crc_eof() - Allocate a page to be used for the trailer CRC + * @skb: The packet to be transmitted + * @tlen: The total length of the trailer * * Returns: 0 for success */ -static int fcoe_get_paged_crc_eof(struct sk_buff *skb, int tlen) +static int fcoe_alloc_paged_crc_eof(struct sk_buff *skb, int tlen) { struct fcoe_percpu_s *fps; - struct page *page; + int rc; fps = &get_cpu_var(fcoe_percpu); - page = fps->crc_eof_page; - if (!page) { - page = alloc_page(GFP_ATOMIC); - if (!page) { - put_cpu_var(fcoe_percpu); - return -ENOMEM; - } - fps->crc_eof_page = page; - fps->crc_eof_offset = 0; - } - - get_page(page); - skb_fill_page_desc(skb, skb_shinfo(skb)->nr_frags, page, - fps->crc_eof_offset, tlen); - skb->len += tlen; - skb->data_len += tlen; - skb->truesize += tlen; - fps->crc_eof_offset += sizeof(struct fcoe_crc_eof); - - if (fps->crc_eof_offset >= PAGE_SIZE) { - fps->crc_eof_page = NULL; - fps->crc_eof_offset = 0; - put_page(page); - } + rc = fcoe_get_paged_crc_eof(skb, tlen, fps); put_cpu_var(fcoe_percpu); - return 0; -} -/** - * fcoe_fc_crc() - calculates FC CRC in this fcoe skb - * @fp: the fc_frame containing data to be checksummed - * - * This uses crc32() to calculate the crc for port frame - * Return : 32 bit crc - */ -u32 fcoe_fc_crc(struct fc_frame *fp) -{ - struct sk_buff *skb = fp_skb(fp); - struct skb_frag_struct *frag; - unsigned char *data; - unsigned long off, len, clen; - u32 crc; - unsigned i; - - crc = crc32(~0, skb->data, skb_headlen(skb)); - - for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { - frag = &skb_shinfo(skb)->frags[i]; - off = frag->page_offset; - len = frag->size; - while (len > 0) { - clen = min(len, PAGE_SIZE - (off & ~PAGE_MASK)); - data = kmap_atomic(frag->page + (off >> PAGE_SHIFT), - KM_SKB_DATA_SOFTIRQ); - crc = crc32(crc, data + (off & ~PAGE_MASK), clen); - kunmap_atomic(data, KM_SKB_DATA_SOFTIRQ); - off += clen; - len -= clen; - } - } - return crc; + return rc; } /** - * fcoe_xmit() - FCoE frame transmit function - * @lp: the associated local fcoe - * @fp: the fc_frame to be transmitted + * fcoe_xmit() - Transmit a FCoE frame + * @lport: The local port that the frame is to be transmitted for + * @fp: The frame to be transmitted * - * Return : 0 for success + * Return: 0 for success */ -int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) +static int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp) { int wlen; u32 crc; struct ethhdr *eh; struct fcoe_crc_eof *cp; struct sk_buff *skb; - struct fcoe_dev_stats *stats; + struct fc_stats *stats; struct fc_frame_header *fh; unsigned int hlen; /* header length implies the version */ unsigned int tlen; /* trailer length */ unsigned int elen; /* eth header, may include vlan */ - struct fcoe_port *port = lport_priv(lp); - struct fcoe_interface *fcoe = port->fcoe; + struct fcoe_port *port = lport_priv(lport); + struct fcoe_interface *fcoe = port->priv; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); u8 sof, eof; struct fcoe_hdr *hp; @@ -1200,13 +1606,13 @@ int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) skb = fp_skb(fp); wlen = skb->len / FCOE_WORD_TO_BYTE; - if (!lp->link_up) { + if (!lport->link_up) { kfree_skb(skb); return 0; } - if (unlikely(fh->fh_r_ctl == FC_RCTL_ELS_REQ) && - fcoe_ctlr_els_send(&fcoe->ctlr, skb)) + if (unlikely(fh->fh_type == FC_TYPE_ELS) && + fcoe_ctlr_els_send(ctlr, lport, skb)) return 0; sof = fr_sof(fp); @@ -1218,8 +1624,8 @@ int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) wlen = (skb->len - tlen + sizeof(crc)) / FCOE_WORD_TO_BYTE; /* crc offload */ - if (likely(lp->crc_offload)) { - skb->ip_summed = CHECKSUM_PARTIAL; + if (likely(lport->crc_offload)) { + skb->ip_summed = CHECKSUM_UNNECESSARY; skb->csum_start = skb_headroom(skb); skb->csum_offset = skb->len; crc = 0; @@ -1231,12 +1637,12 @@ int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) /* copy port crc and eof to the skb buff */ if (skb_is_nonlinear(skb)) { skb_frag_t *frag; - if (fcoe_get_paged_crc_eof(skb, tlen)) { + if (fcoe_alloc_paged_crc_eof(skb, tlen)) { kfree_skb(skb); return -ENOMEM; } frag = &skb_shinfo(skb)->frags[skb_shinfo(skb)->nr_frags - 1]; - cp = kmap_atomic(frag->page, KM_SKB_DATA_SOFTIRQ) + cp = kmap_atomic(skb_frag_page(frag)) + frag->page_offset; } else { cp = (struct fcoe_crc_eof *)skb_put(skb, tlen); @@ -1247,7 +1653,7 @@ int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) cp->fcoe_crc32 = cpu_to_le32(~crc); if (skb_is_nonlinear(skb)) { - kunmap_atomic(cp, KM_SKB_DATA_SOFTIRQ); + kunmap_atomic(cp); cp = NULL; } @@ -1257,21 +1663,30 @@ int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) skb_reset_network_header(skb); skb->mac_len = elen; skb->protocol = htons(ETH_P_FCOE); - skb->dev = fcoe->netdev; + skb->priority = fcoe->priority; + + if (fcoe->netdev->priv_flags & IFF_802_1Q_VLAN && + fcoe->realdev->features & NETIF_F_HW_VLAN_CTAG_TX) { + /* must set skb->dev before calling vlan_put_tag */ + skb->dev = fcoe->realdev; + skb = __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), + vlan_dev_vlan_id(fcoe->netdev)); + if (!skb) + return -ENOMEM; + } else + skb->dev = fcoe->netdev; /* fill up mac and fcoe headers */ eh = eth_hdr(skb); eh->h_proto = htons(ETH_P_FCOE); - if (fcoe->ctlr.map_dest) - fc_fcoe_set_mac(eh->h_dest, fh->fh_d_id); - else - /* insert GW address */ - memcpy(eh->h_dest, fcoe->ctlr.dest_addr, ETH_ALEN); + memcpy(eh->h_dest, ctlr->dest_addr, ETH_ALEN); + if (ctlr->map_dest) + memcpy(eh->h_dest + 3, fh->fh_d_id, 3); - if (unlikely(fcoe->ctlr.flogi_oxid != FC_XID_UNKNOWN)) - memcpy(eh->h_source, fcoe->ctlr.ctl_src_addr, ETH_ALEN); + if (unlikely(ctlr->flogi_oxid != FC_XID_UNKNOWN)) + memcpy(eh->h_source, ctlr->ctl_src_addr, ETH_ALEN); else - memcpy(eh->h_source, fcoe->ctlr.data_src_addr, ETH_ALEN); + memcpy(eh->h_source, port->data_src_addr, ETH_ALEN); hp = (struct fcoe_hdr *)(eh + 1); memset(hp, 0, sizeof(*hp)); @@ -1280,7 +1695,7 @@ int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) hp->fcoe_sof = sof; /* fcoe lso, mss is in max_payload which is non-zero for FCP data */ - if (lp->seq_offload && fr_max_payload(fp)) { + if (lport->seq_offload && fr_max_payload(fp)) { skb_shinfo(skb)->gso_type = SKB_GSO_FCOE; skb_shinfo(skb)->gso_size = fr_max_payload(fp); } else { @@ -1288,23 +1703,20 @@ int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) skb_shinfo(skb)->gso_size = 0; } /* update tx stats: regardless if LLD fails */ - stats = fc_lport_get_stats(lp); + stats = per_cpu_ptr(lport->stats, get_cpu()); stats->TxFrames++; stats->TxWords += wlen; + put_cpu(); /* send down to lld */ - fr_dev(fp) = lp; - if (port->fcoe_pending_queue.qlen) - fcoe_check_wait_queue(lp, skb); - else if (fcoe_start_io(skb)) - fcoe_check_wait_queue(lp, skb); - + fr_dev(fp) = lport; + fcoe_port_send(port, skb); return 0; } /** - * fcoe_percpu_flush_done() - Indicate percpu queue flush completion. - * @skb: the skb being completed. + * fcoe_percpu_flush_done() - Indicate per-CPU queue flush completion + * @skb: The completed skb (argument required by destructor) */ static void fcoe_percpu_flush_done(struct sk_buff *skb) { @@ -1312,251 +1724,288 @@ static void fcoe_percpu_flush_done(struct sk_buff *skb) } /** - * fcoe_percpu_receive_thread() - recv thread per cpu - * @arg: ptr to the fcoe per cpu struct + * fcoe_filter_frames() - filter out bad fcoe frames, i.e. bad CRC + * @lport: The local port the frame was received on + * @fp: The received frame * - * Return: 0 for success + * Return: 0 on passing filtering checks */ -int fcoe_percpu_receive_thread(void *arg) +static inline int fcoe_filter_frames(struct fc_lport *lport, + struct fc_frame *fp) { - struct fcoe_percpu_s *p = arg; - u32 fr_len; - struct fc_lport *lp; - struct fcoe_rcv_info *fr; - struct fcoe_dev_stats *stats; + struct fcoe_ctlr *ctlr; + struct fcoe_interface *fcoe; struct fc_frame_header *fh; - struct sk_buff *skb; - struct fcoe_crc_eof crc_eof; - struct fc_frame *fp; - u8 *mac = NULL; - struct fcoe_port *port; - struct fcoe_hdr *hp; - - set_user_nice(current, -20); + struct sk_buff *skb = (struct sk_buff *)fp; + struct fc_stats *stats; - while (!kthread_should_stop()) { + /* + * We only check CRC if no offload is available and if it is + * it's solicited data, in which case, the FCP layer would + * check it during the copy. + */ + if (lport->crc_offload && skb->ip_summed == CHECKSUM_UNNECESSARY) + fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; + else + fr_flags(fp) |= FCPHF_CRC_UNCHECKED; - spin_lock_bh(&p->fcoe_rx_list.lock); - while ((skb = __skb_dequeue(&p->fcoe_rx_list)) == NULL) { - set_current_state(TASK_INTERRUPTIBLE); - spin_unlock_bh(&p->fcoe_rx_list.lock); - schedule(); - set_current_state(TASK_RUNNING); - if (kthread_should_stop()) - return 0; - spin_lock_bh(&p->fcoe_rx_list.lock); - } - spin_unlock_bh(&p->fcoe_rx_list.lock); - fr = fcoe_dev_from_skb(skb); - lp = fr->fr_dev; - if (unlikely(lp == NULL)) { - if (skb->destructor != fcoe_percpu_flush_done) - FCOE_NETDEV_DBG(skb->dev, "NULL lport in skb"); - kfree_skb(skb); - continue; - } + fh = (struct fc_frame_header *) skb_transport_header(skb); + fh = fc_frame_header_get(fp); + if (fh->fh_r_ctl == FC_RCTL_DD_SOL_DATA && fh->fh_type == FC_TYPE_FCP) + return 0; - FCOE_NETDEV_DBG(skb->dev, "skb_info: len:%d data_len:%d " - "head:%p data:%p tail:%p end:%p sum:%d dev:%s", - skb->len, skb->data_len, - skb->head, skb->data, skb_tail_pointer(skb), - skb_end_pointer(skb), skb->csum, - skb->dev ? skb->dev->name : "<NULL>"); + fcoe = ((struct fcoe_port *)lport_priv(lport))->priv; + ctlr = fcoe_to_ctlr(fcoe); + if (is_fip_mode(ctlr) && fc_frame_payload_op(fp) == ELS_LOGO && + ntoh24(fh->fh_s_id) == FC_FID_FLOGI) { + FCOE_DBG("fcoe: dropping FCoE lport LOGO in fip mode\n"); + return -EINVAL; + } - /* - * Save source MAC address before discarding header. - */ - port = lport_priv(lp); - if (skb_is_nonlinear(skb)) - skb_linearize(skb); /* not ideal */ - mac = eth_hdr(skb)->h_source; + if (!(fr_flags(fp) & FCPHF_CRC_UNCHECKED) || + le32_to_cpu(fr_crc(fp)) == ~crc32(~0, skb->data, skb->len)) { + fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; + return 0; + } - /* - * Frame length checks and setting up the header pointers - * was done in fcoe_rcv already. - */ - hp = (struct fcoe_hdr *) skb_network_header(skb); - fh = (struct fc_frame_header *) skb_transport_header(skb); - - stats = fc_lport_get_stats(lp); - if (unlikely(FC_FCOE_DECAPS_VER(hp) != FC_FCOE_VER)) { - if (stats->ErrorFrames < 5) - printk(KERN_WARNING "fcoe: FCoE version " - "mismatch: The frame has " - "version %x, but the " - "initiator supports version " - "%x\n", FC_FCOE_DECAPS_VER(hp), - FC_FCOE_VER); - stats->ErrorFrames++; - kfree_skb(skb); - continue; - } + stats = per_cpu_ptr(lport->stats, get_cpu()); + stats->InvalidCRCCount++; + if (stats->InvalidCRCCount < 5) + printk(KERN_WARNING "fcoe: dropping frame with CRC error\n"); + put_cpu(); + return -EINVAL; +} - skb_pull(skb, sizeof(struct fcoe_hdr)); - fr_len = skb->len - sizeof(struct fcoe_crc_eof); +/** + * fcoe_recv_frame() - process a single received frame + * @skb: frame to process + */ +static void fcoe_recv_frame(struct sk_buff *skb) +{ + u32 fr_len; + struct fc_lport *lport; + struct fcoe_rcv_info *fr; + struct fc_stats *stats; + struct fcoe_crc_eof crc_eof; + struct fc_frame *fp; + struct fcoe_port *port; + struct fcoe_hdr *hp; - stats->RxFrames++; - stats->RxWords += fr_len / FCOE_WORD_TO_BYTE; + fr = fcoe_dev_from_skb(skb); + lport = fr->fr_dev; + if (unlikely(!lport)) { + if (skb->destructor != fcoe_percpu_flush_done) + FCOE_NETDEV_DBG(skb->dev, "NULL lport in skb\n"); + kfree_skb(skb); + return; + } - fp = (struct fc_frame *)skb; - fc_frame_init(fp); - fr_dev(fp) = lp; - fr_sof(fp) = hp->fcoe_sof; + FCOE_NETDEV_DBG(skb->dev, + "skb_info: len:%d data_len:%d head:%p data:%p tail:%p end:%p sum:%d dev:%s\n", + skb->len, skb->data_len, + skb->head, skb->data, skb_tail_pointer(skb), + skb_end_pointer(skb), skb->csum, + skb->dev ? skb->dev->name : "<NULL>"); - /* Copy out the CRC and EOF trailer for access */ - if (skb_copy_bits(skb, fr_len, &crc_eof, sizeof(crc_eof))) { - kfree_skb(skb); - continue; - } - fr_eof(fp) = crc_eof.fcoe_eof; - fr_crc(fp) = crc_eof.fcoe_crc32; - if (pskb_trim(skb, fr_len)) { - kfree_skb(skb); - continue; - } + port = lport_priv(lport); + skb_linearize(skb); /* check for skb_is_nonlinear is within skb_linearize */ - /* - * We only check CRC if no offload is available and if it is - * it's solicited data, in which case, the FCP layer would - * check it during the copy. - */ - if (lp->crc_offload && skb->ip_summed == CHECKSUM_UNNECESSARY) - fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; - else - fr_flags(fp) |= FCPHF_CRC_UNCHECKED; + /* + * Frame length checks and setting up the header pointers + * was done in fcoe_rcv already. + */ + hp = (struct fcoe_hdr *) skb_network_header(skb); + + stats = per_cpu_ptr(lport->stats, get_cpu()); + if (unlikely(FC_FCOE_DECAPS_VER(hp) != FC_FCOE_VER)) { + if (stats->ErrorFrames < 5) + printk(KERN_WARNING "fcoe: FCoE version " + "mismatch: The frame has " + "version %x, but the " + "initiator supports version " + "%x\n", FC_FCOE_DECAPS_VER(hp), + FC_FCOE_VER); + goto drop; + } - fh = fc_frame_header_get(fp); - if (fh->fh_r_ctl == FC_RCTL_DD_SOL_DATA && - fh->fh_type == FC_TYPE_FCP) { - fc_exch_recv(lp, fp); - continue; - } - if (fr_flags(fp) & FCPHF_CRC_UNCHECKED) { - if (le32_to_cpu(fr_crc(fp)) != - ~crc32(~0, skb->data, fr_len)) { - if (stats->InvalidCRCCount < 5) - printk(KERN_WARNING "fcoe: dropping " - "frame with CRC error\n"); - stats->InvalidCRCCount++; - stats->ErrorFrames++; - fc_frame_free(fp); - continue; - } - fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; - } - if (unlikely(port->fcoe->ctlr.flogi_oxid != FC_XID_UNKNOWN) && - fcoe_ctlr_recv_flogi(&port->fcoe->ctlr, fp, mac)) { - fc_frame_free(fp); - continue; - } - fc_exch_recv(lp, fp); + skb_pull(skb, sizeof(struct fcoe_hdr)); + fr_len = skb->len - sizeof(struct fcoe_crc_eof); + + stats->RxFrames++; + stats->RxWords += fr_len / FCOE_WORD_TO_BYTE; + + fp = (struct fc_frame *)skb; + fc_frame_init(fp); + fr_dev(fp) = lport; + fr_sof(fp) = hp->fcoe_sof; + + /* Copy out the CRC and EOF trailer for access */ + if (skb_copy_bits(skb, fr_len, &crc_eof, sizeof(crc_eof))) + goto drop; + fr_eof(fp) = crc_eof.fcoe_eof; + fr_crc(fp) = crc_eof.fcoe_crc32; + if (pskb_trim(skb, fr_len)) + goto drop; + + if (!fcoe_filter_frames(lport, fp)) { + put_cpu(); + fc_exch_recv(lport, fp); + return; } - return 0; +drop: + stats->ErrorFrames++; + put_cpu(); + kfree_skb(skb); } /** - * fcoe_check_wait_queue() - attempt to clear the transmit backlog - * @lp: the fc_lport - * - * This empties the wait_queue, dequeue the head of the wait_queue queue - * and calls fcoe_start_io() for each packet, if all skb have been - * transmitted, return qlen or -1 if a error occurs, then restore - * wait_queue and try again later. + * fcoe_percpu_receive_thread() - The per-CPU packet receive thread + * @arg: The per-CPU context * - * The wait_queue is used when the skb transmit fails. skb will go - * in the wait_queue which will be emptied by the timer function or - * by the next skb transmit. + * Return: 0 for success */ -static void fcoe_check_wait_queue(struct fc_lport *lp, struct sk_buff *skb) +static int fcoe_percpu_receive_thread(void *arg) { - struct fcoe_port *port = lport_priv(lp); - int rc; - - spin_lock_bh(&port->fcoe_pending_queue.lock); + struct fcoe_percpu_s *p = arg; + struct sk_buff *skb; + struct sk_buff_head tmp; - if (skb) - __skb_queue_tail(&port->fcoe_pending_queue, skb); + skb_queue_head_init(&tmp); - if (port->fcoe_pending_queue_active) - goto out; - port->fcoe_pending_queue_active = 1; + set_user_nice(current, MIN_NICE); - while (port->fcoe_pending_queue.qlen) { - /* keep qlen > 0 until fcoe_start_io succeeds */ - port->fcoe_pending_queue.qlen++; - skb = __skb_dequeue(&port->fcoe_pending_queue); +retry: + while (!kthread_should_stop()) { - spin_unlock_bh(&port->fcoe_pending_queue.lock); - rc = fcoe_start_io(skb); - spin_lock_bh(&port->fcoe_pending_queue.lock); + spin_lock_bh(&p->fcoe_rx_list.lock); + skb_queue_splice_init(&p->fcoe_rx_list, &tmp); - if (rc) { - __skb_queue_head(&port->fcoe_pending_queue, skb); - /* undo temporary increment above */ - port->fcoe_pending_queue.qlen--; - break; + if (!skb_queue_len(&tmp)) { + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_bh(&p->fcoe_rx_list.lock); + schedule(); + set_current_state(TASK_RUNNING); + goto retry; } - /* undo temporary increment above */ - port->fcoe_pending_queue.qlen--; - } - if (port->fcoe_pending_queue.qlen < FCOE_LOW_QUEUE_DEPTH) - lp->qfull = 0; - if (port->fcoe_pending_queue.qlen && !timer_pending(&port->timer)) - mod_timer(&port->timer, jiffies + 2); - port->fcoe_pending_queue_active = 0; -out: - if (port->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH) - lp->qfull = 1; - spin_unlock_bh(&port->fcoe_pending_queue.lock); - return; + spin_unlock_bh(&p->fcoe_rx_list.lock); + + while ((skb = __skb_dequeue(&tmp)) != NULL) + fcoe_recv_frame(skb); + + } + return 0; } /** - * fcoe_dev_setup() - setup link change notification interface + * fcoe_dev_setup() - Setup the link change notification interface */ static void fcoe_dev_setup(void) { + register_dcbevent_notifier(&dcb_notifier); register_netdevice_notifier(&fcoe_notifier); } /** - * fcoe_dev_cleanup() - cleanup link change notification interface + * fcoe_dev_cleanup() - Cleanup the link change notification interface */ static void fcoe_dev_cleanup(void) { + unregister_dcbevent_notifier(&dcb_notifier); unregister_netdevice_notifier(&fcoe_notifier); } +static struct fcoe_interface * +fcoe_hostlist_lookup_realdev_port(struct net_device *netdev) +{ + struct fcoe_interface *fcoe; + struct net_device *real_dev; + + list_for_each_entry(fcoe, &fcoe_hostlist, list) { + if (fcoe->netdev->priv_flags & IFF_802_1Q_VLAN) + real_dev = vlan_dev_real_dev(fcoe->netdev); + else + real_dev = fcoe->netdev; + + if (netdev == real_dev) + return fcoe; + } + return NULL; +} + +static int fcoe_dcb_app_notification(struct notifier_block *notifier, + ulong event, void *ptr) +{ + struct dcb_app_type *entry = ptr; + struct fcoe_ctlr *ctlr; + struct fcoe_interface *fcoe; + struct net_device *netdev; + int prio; + + if (entry->app.selector != DCB_APP_IDTYPE_ETHTYPE) + return NOTIFY_OK; + + netdev = dev_get_by_index(&init_net, entry->ifindex); + if (!netdev) + return NOTIFY_OK; + + fcoe = fcoe_hostlist_lookup_realdev_port(netdev); + dev_put(netdev); + if (!fcoe) + return NOTIFY_OK; + + ctlr = fcoe_to_ctlr(fcoe); + + if (entry->dcbx & DCB_CAP_DCBX_VER_CEE) + prio = ffs(entry->app.priority) - 1; + else + prio = entry->app.priority; + + if (prio < 0) + return NOTIFY_OK; + + if (entry->app.protocol == ETH_P_FIP || + entry->app.protocol == ETH_P_FCOE) + ctlr->priority = prio; + + if (entry->app.protocol == ETH_P_FCOE) + fcoe->priority = prio; + + return NOTIFY_OK; +} + /** - * fcoe_device_notification() - netdev event notification callback - * @notifier: context of the notification - * @event: type of event - * @ptr: fixed array for output parsed ifname + * fcoe_device_notification() - Handler for net device events + * @notifier: The context of the notification + * @event: The type of event + * @ptr: The net device that the event was on * - * This function is called by the ethernet driver in case of link change event + * This function is called by the Ethernet driver in case of link change event. * * Returns: 0 for success */ static int fcoe_device_notification(struct notifier_block *notifier, ulong event, void *ptr) { - struct fc_lport *lp = NULL; - struct net_device *netdev = ptr; + struct fcoe_ctlr_device *cdev; + struct fc_lport *lport = NULL; + struct net_device *netdev = netdev_notifier_info_to_dev(ptr); + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; struct fcoe_port *port; - struct fcoe_dev_stats *stats; + struct fc_stats *stats; u32 link_possible = 1; u32 mfs; int rc = NOTIFY_OK; list_for_each_entry(fcoe, &fcoe_hostlist, list) { if (fcoe->netdev == netdev) { - lp = fcoe->ctlr.lp; + ctlr = fcoe_to_ctlr(fcoe); + lport = ctlr->lp; break; } } - if (lp == NULL) { + if (!lport) { rc = NOTIFY_DONE; goto out; } @@ -1570,248 +2019,438 @@ static int fcoe_device_notification(struct notifier_block *notifier, case NETDEV_CHANGE: break; case NETDEV_CHANGEMTU: + if (netdev->features & NETIF_F_FCOE_MTU) + break; mfs = netdev->mtu - (sizeof(struct fcoe_hdr) + sizeof(struct fcoe_crc_eof)); if (mfs >= FC_MIN_MAX_FRAME) - fc_set_mfs(lp, mfs); + fc_set_mfs(lport, mfs); 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); + port = lport_priv(ctlr->lp); + queue_work(fcoe_wq, &port->destroy_work); goto out; break; + case NETDEV_FEAT_CHANGE: + fcoe_netdev_features_change(lport, netdev); + break; default: FCOE_NETDEV_DBG(netdev, "Unknown event %ld " "from netdev netlink\n", event); } - if (link_possible && !fcoe_link_ok(lp)) - fcoe_ctlr_link_up(&fcoe->ctlr); - else if (fcoe_ctlr_link_down(&fcoe->ctlr)) { - stats = fc_lport_get_stats(lp); - stats->LinkFailureCount++; - fcoe_clean_pending_queue(lp); + + fcoe_link_speed_update(lport); + + cdev = fcoe_ctlr_to_ctlr_dev(ctlr); + + if (link_possible && !fcoe_link_ok(lport)) { + switch (cdev->enabled) { + case FCOE_CTLR_DISABLED: + pr_info("Link up while interface is disabled.\n"); + break; + case FCOE_CTLR_ENABLED: + case FCOE_CTLR_UNUSED: + fcoe_ctlr_link_up(ctlr); + }; + } else if (fcoe_ctlr_link_down(ctlr)) { + switch (cdev->enabled) { + case FCOE_CTLR_DISABLED: + pr_info("Link down while interface is disabled.\n"); + break; + case FCOE_CTLR_ENABLED: + case FCOE_CTLR_UNUSED: + stats = per_cpu_ptr(lport->stats, get_cpu()); + stats->LinkFailureCount++; + put_cpu(); + fcoe_clean_pending_queue(lport); + }; } out: return rc; } /** - * fcoe_if_to_netdev() - parse a name buffer to get netdev - * @buffer: incoming buffer to be copied + * fcoe_disable() - Disables a FCoE interface + * @netdev : The net_device object the Ethernet interface to create on + * + * Called from fcoe transport. + * + * Returns: 0 for success * - * Returns: NULL or ptr to net_device + * Deprecated: use fcoe_ctlr_enabled() */ -static struct net_device *fcoe_if_to_netdev(const char *buffer) +static int fcoe_disable(struct net_device *netdev) { - char *cp; - char ifname[IFNAMSIZ + 2]; + struct fcoe_ctlr *ctlr; + struct fcoe_interface *fcoe; + int rc = 0; - if (buffer) { - strlcpy(ifname, buffer, IFNAMSIZ); - cp = ifname + strlen(ifname); - while (--cp >= ifname && *cp == '\n') - *cp = '\0'; - return dev_get_by_name(&init_net, ifname); - } - return NULL; + mutex_lock(&fcoe_config_mutex); + + rtnl_lock(); + fcoe = fcoe_hostlist_lookup_port(netdev); + rtnl_unlock(); + + if (fcoe) { + ctlr = fcoe_to_ctlr(fcoe); + fcoe_ctlr_link_down(ctlr); + fcoe_clean_pending_queue(ctlr->lp); + } else + rc = -ENODEV; + + mutex_unlock(&fcoe_config_mutex); + return rc; } /** - * fcoe_destroy() - handles the destroy from sysfs - * @buffer: expected to be an eth if name - * @kp: associated kernel param + * fcoe_enable() - Enables a FCoE interface + * @netdev : The net_device object the Ethernet interface to create on + * + * Called from fcoe transport. * * Returns: 0 for success */ -static int fcoe_destroy(const char *buffer, struct kernel_param *kp) +static int fcoe_enable(struct net_device *netdev) { + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; - struct net_device *netdev; - int rc; + int rc = 0; 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(); + fcoe = fcoe_hostlist_lookup_port(netdev); + rtnl_unlock(); - netdev = fcoe_if_to_netdev(buffer); - if (!netdev) { + if (!fcoe) { rc = -ENODEV; - goto out_nodev; + goto out; } + ctlr = fcoe_to_ctlr(fcoe); + + if (!fcoe_link_ok(ctlr->lp)) + fcoe_ctlr_link_up(ctlr); + +out: + mutex_unlock(&fcoe_config_mutex); + return rc; +} + +/** + * fcoe_ctlr_enabled() - Enable or disable an FCoE Controller + * @cdev: The FCoE Controller that is being enabled or disabled + * + * fcoe_sysfs will ensure that the state of 'enabled' has + * changed, so no checking is necessary here. This routine simply + * calls fcoe_enable or fcoe_disable, both of which are deprecated. + * When those routines are removed the functionality can be merged + * here. + */ +static int fcoe_ctlr_enabled(struct fcoe_ctlr_device *cdev) +{ + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(cdev); + struct fc_lport *lport = ctlr->lp; + struct net_device *netdev = fcoe_netdev(lport); + + switch (cdev->enabled) { + case FCOE_CTLR_ENABLED: + return fcoe_enable(netdev); + case FCOE_CTLR_DISABLED: + return fcoe_disable(netdev); + case FCOE_CTLR_UNUSED: + default: + return -ENOTSUPP; + }; +} + +/** + * fcoe_destroy() - Destroy a FCoE interface + * @netdev : The net_device object the Ethernet interface to create on + * + * Called from fcoe transport + * + * Returns: 0 for success + */ +static int fcoe_destroy(struct net_device *netdev) +{ + struct fcoe_ctlr *ctlr; + struct fcoe_interface *fcoe; + struct fc_lport *lport; + struct fcoe_port *port; + int rc = 0; + + mutex_lock(&fcoe_config_mutex); rtnl_lock(); fcoe = fcoe_hostlist_lookup_port(netdev); if (!fcoe) { - rtnl_unlock(); rc = -ENODEV; - goto out_putdev; + goto out_nodev; } + ctlr = fcoe_to_ctlr(fcoe); + lport = ctlr->lp; + port = lport_priv(lport); list_del(&fcoe->list); - fcoe_interface_cleanup(fcoe); - rtnl_unlock(); - fcoe_if_destroy(fcoe->ctlr.lp); -out_putdev: - dev_put(netdev); + queue_work(fcoe_wq, &port->destroy_work); out_nodev: + rtnl_unlock(); mutex_unlock(&fcoe_config_mutex); return rc; } +/** + * fcoe_destroy_work() - Destroy a FCoE port in a deferred work context + * @work: Handle to the FCoE port to be destroyed + */ static void fcoe_destroy_work(struct work_struct *work) { + struct fcoe_ctlr_device *cdev; + struct fcoe_ctlr *ctlr; struct fcoe_port *port; + struct fcoe_interface *fcoe; + struct Scsi_Host *shost; + struct fc_host_attrs *fc_host; + unsigned long flags; + struct fc_vport *vport; + struct fc_vport *next_vport; port = container_of(work, struct fcoe_port, destroy_work); + shost = port->lport->host; + fc_host = shost_to_fc_host(shost); + + /* Loop through all the vports and mark them for deletion */ + spin_lock_irqsave(shost->host_lock, flags); + list_for_each_entry_safe(vport, next_vport, &fc_host->vports, peers) { + if (vport->flags & (FC_VPORT_DEL | FC_VPORT_CREATING)) { + continue; + } else { + vport->flags |= FC_VPORT_DELETING; + queue_work(fc_host_work_q(shost), + &vport->vport_delete_work); + } + } + spin_unlock_irqrestore(shost->host_lock, flags); + + flush_workqueue(fc_host_work_q(shost)); + mutex_lock(&fcoe_config_mutex); + + fcoe = port->priv; + ctlr = fcoe_to_ctlr(fcoe); + cdev = fcoe_ctlr_to_ctlr_dev(ctlr); + fcoe_if_destroy(port->lport); + fcoe_interface_cleanup(fcoe); + mutex_unlock(&fcoe_config_mutex); + + fcoe_ctlr_device_delete(cdev); } /** - * fcoe_create() - Handles the create call from sysfs - * @buffer: expected to be an eth if name - * @kp: associated kernel param + * fcoe_match() - Check if the FCoE is supported on the given netdevice + * @netdev : The net_device object the Ethernet interface to create on * - * Returns: 0 for success + * Called from fcoe transport. + * + * Returns: always returns true as this is the default FCoE transport, + * i.e., support all netdevs. */ -static int fcoe_create(const char *buffer, struct kernel_param *kp) +static bool fcoe_match(struct net_device *netdev) { - int rc; - struct fcoe_interface *fcoe; - struct fc_lport *lport; - struct net_device *netdev; + return true; +} - 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; +/** + * fcoe_dcb_create() - Initialize DCB attributes and hooks + * @netdev: The net_device object of the L2 link that should be queried + * @port: The fcoe_port to bind FCoE APP priority with + * @ + */ +static void fcoe_dcb_create(struct fcoe_interface *fcoe) +{ +#ifdef CONFIG_DCB + int dcbx; + u8 fup, up; + struct net_device *netdev = fcoe->realdev; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); + struct dcb_app app = { + .priority = 0, + .protocol = ETH_P_FCOE + }; + + /* setup DCB priority attributes. */ + if (netdev && netdev->dcbnl_ops && netdev->dcbnl_ops->getdcbx) { + dcbx = netdev->dcbnl_ops->getdcbx(netdev); + + if (dcbx & DCB_CAP_DCBX_VER_IEEE) { + app.selector = IEEE_8021QAZ_APP_SEL_ETHERTYPE; + up = dcb_ieee_getapp_mask(netdev, &app); + app.protocol = ETH_P_FIP; + fup = dcb_ieee_getapp_mask(netdev, &app); + } else { + app.selector = DCB_APP_IDTYPE_ETHTYPE; + up = dcb_getapp(netdev, &app); + app.protocol = ETH_P_FIP; + fup = dcb_getapp(netdev, &app); + } + + fcoe->priority = ffs(up) ? ffs(up) - 1 : 0; + ctlr->priority = ffs(fup) ? ffs(fup) - 1 : fcoe->priority; } #endif +} +enum fcoe_create_link_state { + FCOE_CREATE_LINK_DOWN, + FCOE_CREATE_LINK_UP, +}; + +/** + * _fcoe_create() - (internal) Create a fcoe interface + * @netdev : The net_device object the Ethernet interface to create on + * @fip_mode: The FIP mode for this creation + * @link_state: The ctlr link state on creation + * + * Called from either the libfcoe 'create' module parameter + * via fcoe_create or from fcoe_syfs's ctlr_create file. + * + * libfcoe's 'create' module parameter is deprecated so some + * consolidation of code can be done when that interface is + * removed. + */ +static int _fcoe_create(struct net_device *netdev, enum fip_state fip_mode, + enum fcoe_create_link_state link_state) +{ + int rc = 0; + struct fcoe_ctlr_device *ctlr_dev; + struct fcoe_ctlr *ctlr; + struct fcoe_interface *fcoe; + struct fc_lport *lport; + + mutex_lock(&fcoe_config_mutex); 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; + goto out_nodev; } - fcoe = fcoe_interface_create(netdev); - if (!fcoe) { - rc = -ENOMEM; - goto out_putdev; + fcoe = fcoe_interface_create(netdev, fip_mode); + if (IS_ERR(fcoe)) { + rc = PTR_ERR(fcoe); + goto out_nodev; } - lport = fcoe_if_create(fcoe, &netdev->dev); + ctlr = fcoe_to_ctlr(fcoe); + ctlr_dev = fcoe_ctlr_to_ctlr_dev(ctlr); + lport = fcoe_if_create(fcoe, &ctlr_dev->dev, 0); if (IS_ERR(lport)) { printk(KERN_ERR "fcoe: Failed to create interface (%s)\n", netdev->name); rc = -EIO; + rtnl_unlock(); fcoe_interface_cleanup(fcoe); - goto out_free; + mutex_unlock(&fcoe_config_mutex); + fcoe_ctlr_device_delete(ctlr_dev); + goto out; } /* Make this the "master" N_Port */ - fcoe->ctlr.lp = lport; + ctlr->lp = lport; - /* add to lports list */ - fcoe_hostlist_add(lport); + /* setup DCB priority attributes. */ + fcoe_dcb_create(fcoe); /* 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(). + * If the fcoe_ctlr_device is to be set to DISABLED + * it must be done after the lport is added to the + * hostlist, but before the rtnl_lock is released. + * This is because the rtnl_lock protects the + * hostlist that fcoe_device_notification uses. If + * the FCoE Controller is intended to be created + * DISABLED then 'enabled' needs to be considered + * handling link events. 'enabled' must be set + * before the lport can be found in the hostlist + * when a link up event is received. */ - fcoe_interface_put(fcoe); -out_putdev: - dev_put(netdev); + if (link_state == FCOE_CREATE_LINK_UP) + ctlr_dev->enabled = FCOE_CTLR_ENABLED; + else + ctlr_dev->enabled = FCOE_CTLR_DISABLED; + + if (link_state == FCOE_CREATE_LINK_UP && + !fcoe_link_ok(lport)) { + rtnl_unlock(); + fcoe_ctlr_link_up(ctlr); + mutex_unlock(&fcoe_config_mutex); + return rc; + } + out_nodev: rtnl_unlock(); mutex_unlock(&fcoe_config_mutex); +out: return rc; } -module_param_call(create, fcoe_create, NULL, NULL, S_IWUSR); -__MODULE_PARM_TYPE(create, "string"); -MODULE_PARM_DESC(create, "Create fcoe fcoe using net device passed in."); -module_param_call(destroy, fcoe_destroy, NULL, NULL, S_IWUSR); -__MODULE_PARM_TYPE(destroy, "string"); -MODULE_PARM_DESC(destroy, "Destroy fcoe fcoe"); - /** - * fcoe_link_ok() - Check if link is ok for the fc_lport - * @lp: ptr to the fc_lport + * fcoe_create() - Create a fcoe interface + * @netdev : The net_device object the Ethernet interface to create on + * @fip_mode: The FIP mode for this creation * - * Any permanently-disqualifying conditions have been previously checked. - * This also updates the speed setting, which may change with link for 100/1000. + * Called from fcoe transport * - * This function should probably be checking for PAUSE support at some point - * in the future. Currently Per-priority-pause is not determinable using - * ethtool, so we shouldn't be restrictive until that problem is resolved. + * Returns: 0 for success + */ +static int fcoe_create(struct net_device *netdev, enum fip_state fip_mode) +{ + return _fcoe_create(netdev, fip_mode, FCOE_CREATE_LINK_UP); +} + +/** + * fcoe_ctlr_alloc() - Allocate a fcoe interface from fcoe_sysfs + * @netdev: The net_device to be used by the allocated FCoE Controller * - * Returns: 0 if link is OK for use by FCoE. + * This routine is called from fcoe_sysfs. It will start the fcoe_ctlr + * in a link_down state. The allows the user an opportunity to configure + * the FCoE Controller from sysfs before enabling the FCoE Controller. * + * Creating in with this routine starts the FCoE Controller in Fabric + * mode. The user can change to VN2VN or another mode before enabling. */ -int fcoe_link_ok(struct fc_lport *lp) +static int fcoe_ctlr_alloc(struct net_device *netdev) { - struct fcoe_port *port = lport_priv(lp); - struct net_device *dev = port->fcoe->netdev; - struct ethtool_cmd ecmd = { ETHTOOL_GSET }; + return _fcoe_create(netdev, FIP_MODE_FABRIC, + FCOE_CREATE_LINK_DOWN); +} - if ((dev->flags & IFF_UP) && netif_carrier_ok(dev) && - (!dev_ethtool_get_settings(dev, &ecmd))) { - lp->link_supported_speeds &= - ~(FC_PORTSPEED_1GBIT | FC_PORTSPEED_10GBIT); - if (ecmd.supported & (SUPPORTED_1000baseT_Half | - SUPPORTED_1000baseT_Full)) - lp->link_supported_speeds |= FC_PORTSPEED_1GBIT; - if (ecmd.supported & SUPPORTED_10000baseT_Full) - lp->link_supported_speeds |= - FC_PORTSPEED_10GBIT; - if (ecmd.speed == SPEED_1000) - lp->link_speed = FC_PORTSPEED_1GBIT; - if (ecmd.speed == SPEED_10000) - lp->link_speed = FC_PORTSPEED_10GBIT; +/** + * fcoe_link_ok() - Check if the link is OK for a local port + * @lport: The local port to check link on + * + * Returns: 0 if link is UP and OK, -1 if not + * + */ +static int fcoe_link_ok(struct fc_lport *lport) +{ + struct net_device *netdev = fcoe_netdev(lport); + if (netif_oper_up(netdev)) return 0; - } return -1; } /** - * fcoe_percpu_clean() - Clear the pending skbs for an lport - * @lp: the fc_lport + * fcoe_percpu_clean() - Clear all pending skbs for an local port + * @lport: The local port whose skbs are to be cleared * * Must be called with fcoe_create_mutex held to single-thread completion. * @@ -1820,42 +2459,25 @@ int fcoe_link_ok(struct fc_lport *lp) * 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) +static void fcoe_percpu_clean(struct fc_lport *lport) { struct fcoe_percpu_s *pp; - struct fcoe_rcv_info *fr; - struct sk_buff_head *list; - struct sk_buff *skb, *next; - struct sk_buff *head; + struct sk_buff *skb; unsigned int cpu; for_each_possible_cpu(cpu) { pp = &per_cpu(fcoe_percpu, cpu); - spin_lock_bh(&pp->fcoe_rx_list.lock); - list = &pp->fcoe_rx_list; - head = list->next; - for (skb = head; skb != (struct sk_buff *)list; - skb = next) { - next = skb->next; - fr = fcoe_dev_from_skb(skb); - if (fr->fr_dev == lp) { - __skb_unlink(skb, list); - kfree_skb(skb); - } - } - if (!pp->thread || !cpu_online(cpu)) { - spin_unlock_bh(&pp->fcoe_rx_list.lock); + if (!pp->thread || !cpu_online(cpu)) continue; - } skb = dev_alloc_skb(0); - if (!skb) { - spin_unlock_bh(&pp->fcoe_rx_list.lock); + if (!skb) continue; - } + skb->destructor = fcoe_percpu_flush_done; + spin_lock_bh(&pp->fcoe_rx_list.lock); __skb_queue_tail(&pp->fcoe_rx_list, skb); if (pp->fcoe_rx_list.qlen == 1) wake_up_process(pp->thread); @@ -1866,78 +2488,75 @@ void fcoe_percpu_clean(struct fc_lport *lp) } /** - * fcoe_clean_pending_queue() - Dequeue a skb and free it - * @lp: the corresponding fc_lport + * fcoe_reset() - Reset a local port + * @shost: The SCSI host associated with the local port to be reset * - * Returns: none + * Returns: Always 0 (return value required by FC transport template) */ -void fcoe_clean_pending_queue(struct fc_lport *lp) +static int fcoe_reset(struct Scsi_Host *shost) { - struct fcoe_port *port = lport_priv(lp); - struct sk_buff *skb; + struct fc_lport *lport = shost_priv(shost); + struct fcoe_port *port = lport_priv(lport); + struct fcoe_interface *fcoe = port->priv; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); + struct fcoe_ctlr_device *cdev = fcoe_ctlr_to_ctlr_dev(ctlr); - spin_lock_bh(&port->fcoe_pending_queue.lock); - while ((skb = __skb_dequeue(&port->fcoe_pending_queue)) != NULL) { - spin_unlock_bh(&port->fcoe_pending_queue.lock); - kfree_skb(skb); - spin_lock_bh(&port->fcoe_pending_queue.lock); - } - spin_unlock_bh(&port->fcoe_pending_queue.lock); -} + fcoe_ctlr_link_down(ctlr); + fcoe_clean_pending_queue(ctlr->lp); -/** - * fcoe_reset() - Resets the fcoe - * @shost: shost the reset is from - * - * Returns: always 0 - */ -int fcoe_reset(struct Scsi_Host *shost) -{ - struct fc_lport *lport = shost_priv(shost); - fc_lport_reset(lport); + if (cdev->enabled != FCOE_CTLR_DISABLED && + !fcoe_link_ok(ctlr->lp)) + fcoe_ctlr_link_up(ctlr); return 0; } /** - * fcoe_hostlist_lookup_port() - find the corresponding lport by a given device - * @dev: this is currently ptr to net_device + * fcoe_hostlist_lookup_port() - Find the FCoE interface associated with a net device + * @netdev: The net device used as a key * - * Returns: NULL or the located fcoe_port - * Locking: must be called with the RNL mutex held + * Locking: Must be called with the RNL mutex held. + * + * Returns: NULL or the FCoE interface */ static struct fcoe_interface * -fcoe_hostlist_lookup_port(const struct net_device *dev) +fcoe_hostlist_lookup_port(const struct net_device *netdev) { struct fcoe_interface *fcoe; list_for_each_entry(fcoe, &fcoe_hostlist, list) { - if (fcoe->netdev == dev) + if (fcoe->netdev == netdev) return fcoe; } return NULL; } /** - * fcoe_hostlist_lookup() - Find the corresponding lport by netdev - * @netdev: ptr to net_device + * fcoe_hostlist_lookup() - Find the local port associated with a + * given net device + * @netdev: The netdevice used as a key * - * Returns: 0 for success - * Locking: must be called with the RTNL mutex held + * Locking: Must be called with the RTNL mutex held + * + * Returns: NULL or the local port */ static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev) { + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; fcoe = fcoe_hostlist_lookup_port(netdev); - return (fcoe) ? fcoe->ctlr.lp : NULL; + ctlr = fcoe_to_ctlr(fcoe); + return (fcoe) ? ctlr->lp : NULL; } /** - * fcoe_hostlist_add() - Add a lport to lports list - * @lp: ptr to the fc_lport to be added + * fcoe_hostlist_add() - Add the FCoE interface identified by a local + * port to the hostlist + * @lport: The local port that identifies the FCoE interface to be added * - * Returns: 0 for success * Locking: must be called with the RTNL mutex held + * + * Returns: 0 for success */ static int fcoe_hostlist_add(const struct fc_lport *lport) { @@ -1947,22 +2566,65 @@ static int fcoe_hostlist_add(const struct fc_lport *lport) fcoe = fcoe_hostlist_lookup_port(fcoe_netdev(lport)); if (!fcoe) { port = lport_priv(lport); - fcoe = port->fcoe; + fcoe = port->priv; list_add_tail(&fcoe->list, &fcoe_hostlist); } return 0; } /** - * fcoe_init() - fcoe module loading initialization + * fcoe_hostlist_del() - Remove the FCoE interface identified by a local + * port to the hostlist + * @lport: The local port that identifies the FCoE interface to be added + * + * Locking: must be called with the RTNL mutex held * - * Returns 0 on success, negative on failure + */ +static void fcoe_hostlist_del(const struct fc_lport *lport) +{ + struct fcoe_interface *fcoe; + struct fcoe_port *port; + + port = lport_priv(lport); + fcoe = port->priv; + list_del(&fcoe->list); + return; +} + +static struct fcoe_transport fcoe_sw_transport = { + .name = {FCOE_TRANSPORT_DEFAULT}, + .attached = false, + .list = LIST_HEAD_INIT(fcoe_sw_transport.list), + .match = fcoe_match, + .alloc = fcoe_ctlr_alloc, + .create = fcoe_create, + .destroy = fcoe_destroy, + .enable = fcoe_enable, + .disable = fcoe_disable, +}; + +/** + * fcoe_init() - Initialize fcoe.ko + * + * Returns: 0 on success, or a negative value on failure */ static int __init fcoe_init(void) { + struct fcoe_percpu_s *p; unsigned int cpu; int rc = 0; - struct fcoe_percpu_s *p; + + fcoe_wq = alloc_workqueue("fcoe", 0, 0); + if (!fcoe_wq) + return -ENOMEM; + + /* register as a fcoe transport */ + rc = fcoe_transport_attach(&fcoe_sw_transport); + if (rc) { + printk(KERN_ERR "failed to register an fcoe transport, check " + "if libfcoe is loaded\n"); + return rc; + } mutex_lock(&fcoe_config_mutex); @@ -1971,14 +2633,18 @@ static int __init fcoe_init(void) skb_queue_head_init(&p->fcoe_rx_list); } + cpu_notifier_register_begin(); + for_each_online_cpu(cpu) fcoe_percpu_thread_create(cpu); /* Initialize per CPU interrupt thread */ - rc = register_hotcpu_notifier(&fcoe_cpu_notifier); + rc = __register_hotcpu_notifier(&fcoe_cpu_notifier); if (rc) goto out_free; + cpu_notifier_register_done(); + /* Setup link change notification */ fcoe_dev_setup(); @@ -1993,21 +2659,26 @@ out_free: for_each_online_cpu(cpu) { fcoe_percpu_thread_destroy(cpu); } + + cpu_notifier_register_done(); + mutex_unlock(&fcoe_config_mutex); + destroy_workqueue(fcoe_wq); return rc; } module_init(fcoe_init); /** - * fcoe_exit() - fcoe module unloading cleanup + * fcoe_exit() - Clean up fcoe.ko * - * Returns 0 on success, negative on failure + * Returns: 0 on success or a negative value on failure */ static void __exit fcoe_exit(void) { - unsigned int cpu; struct fcoe_interface *fcoe, *tmp; + struct fcoe_ctlr *ctlr; struct fcoe_port *port; + unsigned int cpu; mutex_lock(&fcoe_config_mutex); @@ -2016,26 +2687,281 @@ static void __exit fcoe_exit(void) /* releases the associated fcoe hosts */ 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); + ctlr = fcoe_to_ctlr(fcoe); + port = lport_priv(ctlr->lp); + fcoe_hostlist_del(port->lport); + queue_work(fcoe_wq, &port->destroy_work); } rtnl_unlock(); - unregister_hotcpu_notifier(&fcoe_cpu_notifier); + cpu_notifier_register_begin(); for_each_online_cpu(cpu) fcoe_percpu_thread_destroy(cpu); + __unregister_hotcpu_notifier(&fcoe_cpu_notifier); + + cpu_notifier_register_done(); + mutex_unlock(&fcoe_config_mutex); - /* flush any asyncronous interface destroys, - * this should happen after the netdev notifier is unregistered */ - flush_scheduled_work(); + /* + * destroy_work's may be chained but destroy_workqueue() + * can take care of them. Just kill the fcoe_wq. + */ + destroy_workqueue(fcoe_wq); - /* detach from scsi transport - * must happen after all destroys are done, therefor after the flush */ + /* + * Detaching from the scsi transport must happen after all + * destroys are done on the fcoe_wq. destroy_workqueue will + * enusre the fcoe_wq is flushed. + */ fcoe_if_exit(); + + /* detach from fcoe transport */ + fcoe_transport_detach(&fcoe_sw_transport); } module_exit(fcoe_exit); + +/** + * fcoe_flogi_resp() - FCoE specific FLOGI and FDISC response handler + * @seq: active sequence in the FLOGI or FDISC exchange + * @fp: response frame, or error encoded in a pointer (timeout) + * @arg: pointer the the fcoe_ctlr structure + * + * This handles MAC address management for FCoE, then passes control on to + * the libfc FLOGI response handler. + */ +static void fcoe_flogi_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg) +{ + struct fcoe_ctlr *fip = arg; + struct fc_exch *exch = fc_seq_exch(seq); + struct fc_lport *lport = exch->lp; + u8 *mac; + + if (IS_ERR(fp)) + goto done; + + mac = fr_cb(fp)->granted_mac; + /* pre-FIP */ + if (is_zero_ether_addr(mac)) + fcoe_ctlr_recv_flogi(fip, lport, fp); + if (!is_zero_ether_addr(mac)) + fcoe_update_src_mac(lport, mac); +done: + fc_lport_flogi_resp(seq, fp, lport); +} + +/** + * fcoe_logo_resp() - FCoE specific LOGO response handler + * @seq: active sequence in the LOGO exchange + * @fp: response frame, or error encoded in a pointer (timeout) + * @arg: pointer the the fcoe_ctlr structure + * + * This handles MAC address management for FCoE, then passes control on to + * the libfc LOGO response handler. + */ +static void fcoe_logo_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg) +{ + struct fc_lport *lport = arg; + static u8 zero_mac[ETH_ALEN] = { 0 }; + + if (!IS_ERR(fp)) + fcoe_update_src_mac(lport, zero_mac); + fc_lport_logo_resp(seq, fp, lport); +} + +/** + * fcoe_elsct_send - FCoE specific ELS handler + * + * This does special case handling of FIP encapsualted ELS exchanges for FCoE, + * using FCoE specific response handlers and passing the FIP controller as + * the argument (the lport is still available from the exchange). + * + * Most of the work here is just handed off to the libfc routine. + */ +static struct fc_seq *fcoe_elsct_send(struct fc_lport *lport, u32 did, + struct fc_frame *fp, unsigned int op, + void (*resp)(struct fc_seq *, + struct fc_frame *, + void *), + void *arg, u32 timeout) +{ + struct fcoe_port *port = lport_priv(lport); + struct fcoe_interface *fcoe = port->priv; + struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe); + struct fc_frame_header *fh = fc_frame_header_get(fp); + + switch (op) { + case ELS_FLOGI: + case ELS_FDISC: + if (lport->point_to_multipoint) + break; + return fc_elsct_send(lport, did, fp, op, fcoe_flogi_resp, + fip, timeout); + case ELS_LOGO: + /* only hook onto fabric logouts, not port logouts */ + if (ntoh24(fh->fh_d_id) != FC_FID_FLOGI) + break; + return fc_elsct_send(lport, did, fp, op, fcoe_logo_resp, + lport, timeout); + } + return fc_elsct_send(lport, did, fp, op, resp, arg, timeout); +} + +/** + * fcoe_vport_create() - create an fc_host/scsi_host for a vport + * @vport: fc_vport object to create a new fc_host for + * @disabled: start the new fc_host in a disabled state by default? + * + * Returns: 0 for success + */ +static int fcoe_vport_create(struct fc_vport *vport, bool disabled) +{ + struct Scsi_Host *shost = vport_to_shost(vport); + struct fc_lport *n_port = shost_priv(shost); + struct fcoe_port *port = lport_priv(n_port); + struct fcoe_interface *fcoe = port->priv; + struct net_device *netdev = fcoe->netdev; + struct fc_lport *vn_port; + int rc; + char buf[32]; + + rc = fcoe_validate_vport_create(vport); + if (rc) { + fcoe_wwn_to_str(vport->port_name, buf, sizeof(buf)); + printk(KERN_ERR "fcoe: Failed to create vport, " + "WWPN (0x%s) already exists\n", + buf); + return rc; + } + + mutex_lock(&fcoe_config_mutex); + rtnl_lock(); + vn_port = fcoe_if_create(fcoe, &vport->dev, 1); + rtnl_unlock(); + mutex_unlock(&fcoe_config_mutex); + + if (IS_ERR(vn_port)) { + printk(KERN_ERR "fcoe: fcoe_vport_create(%s) failed\n", + netdev->name); + return -EIO; + } + + if (disabled) { + fc_vport_set_state(vport, FC_VPORT_DISABLED); + } else { + vn_port->boot_time = jiffies; + fc_fabric_login(vn_port); + fc_vport_setlink(vn_port); + } + return 0; +} + +/** + * fcoe_vport_destroy() - destroy the fc_host/scsi_host for a vport + * @vport: fc_vport object that is being destroyed + * + * Returns: 0 for success + */ +static int fcoe_vport_destroy(struct fc_vport *vport) +{ + struct Scsi_Host *shost = vport_to_shost(vport); + struct fc_lport *n_port = shost_priv(shost); + struct fc_lport *vn_port = vport->dd_data; + + mutex_lock(&n_port->lp_mutex); + list_del(&vn_port->list); + mutex_unlock(&n_port->lp_mutex); + + mutex_lock(&fcoe_config_mutex); + fcoe_if_destroy(vn_port); + mutex_unlock(&fcoe_config_mutex); + + return 0; +} + +/** + * fcoe_vport_disable() - change vport state + * @vport: vport to bring online/offline + * @disable: should the vport be disabled? + */ +static int fcoe_vport_disable(struct fc_vport *vport, bool disable) +{ + struct fc_lport *lport = vport->dd_data; + + if (disable) { + fc_vport_set_state(vport, FC_VPORT_DISABLED); + fc_fabric_logoff(lport); + } else { + lport->boot_time = jiffies; + fc_fabric_login(lport); + fc_vport_setlink(lport); + } + + return 0; +} + +/** + * fcoe_vport_set_symbolic_name() - append vport string to symbolic name + * @vport: fc_vport with a new symbolic name string + * + * After generating a new symbolic name string, a new RSPN_ID request is + * sent to the name server. There is no response handler, so if it fails + * for some reason it will not be retried. + */ +static void fcoe_set_vport_symbolic_name(struct fc_vport *vport) +{ + struct fc_lport *lport = vport->dd_data; + struct fc_frame *fp; + size_t len; + + snprintf(fc_host_symbolic_name(lport->host), FC_SYMBOLIC_NAME_SIZE, + "%s v%s over %s : %s", FCOE_NAME, FCOE_VERSION, + fcoe_netdev(lport)->name, vport->symbolic_name); + + if (lport->state != LPORT_ST_READY) + return; + + len = strnlen(fc_host_symbolic_name(lport->host), 255); + fp = fc_frame_alloc(lport, + sizeof(struct fc_ct_hdr) + + sizeof(struct fc_ns_rspn) + len); + if (!fp) + return; + lport->tt.elsct_send(lport, FC_FID_DIR_SERV, fp, FC_NS_RSPN_ID, + NULL, NULL, 3 * lport->r_a_tov); +} + +static void fcoe_fcf_get_vlan_id(struct fcoe_fcf_device *fcf_dev) +{ + struct fcoe_ctlr_device *ctlr_dev = + fcoe_fcf_dev_to_ctlr_dev(fcf_dev); + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + struct fcoe_interface *fcoe = fcoe_ctlr_priv(ctlr); + + fcf_dev->vlan_id = vlan_dev_vlan_id(fcoe->netdev); +} + +/** + * fcoe_set_port_id() - Callback from libfc when Port_ID is set. + * @lport: the local port + * @port_id: the port ID + * @fp: the received frame, if any, that caused the port_id to be set. + * + * This routine handles the case where we received a FLOGI and are + * entering point-to-point mode. We need to call fcoe_ctlr_recv_flogi() + * so it can set the non-mapped mode and gateway address. + * + * The FLOGI LS_ACC is handled by fcoe_flogi_resp(). + */ +static void fcoe_set_port_id(struct fc_lport *lport, + u32 port_id, struct fc_frame *fp) +{ + struct fcoe_port *port = lport_priv(lport); + struct fcoe_interface *fcoe = port->priv; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); + + if (fp && fc_frame_payload_op(fp) == ELS_FLOGI) + fcoe_ctlr_recv_flogi(ctlr, lport, fp); +} |
