diff options
Diffstat (limited to 'drivers/scsi/fcoe')
-rw-r--r-- | drivers/scsi/fcoe/Makefile | 5 | ||||
-rw-r--r-- | drivers/scsi/fcoe/fcoe.c | 2837 | ||||
-rw-r--r-- | drivers/scsi/fcoe/fcoe.h | 93 | ||||
-rw-r--r-- | drivers/scsi/fcoe/fcoe_ctlr.c | 3266 | ||||
-rw-r--r-- | drivers/scsi/fcoe/fcoe_sysfs.c | 1066 | ||||
-rw-r--r-- | drivers/scsi/fcoe/fcoe_transport.c | 1058 | ||||
-rw-r--r-- | drivers/scsi/fcoe/libfcoe.h | 36 |
7 files changed, 8361 insertions, 0 deletions
diff --git a/drivers/scsi/fcoe/Makefile b/drivers/scsi/fcoe/Makefile new file mode 100644 index 000000000..1183e80a0 --- /dev/null +++ b/drivers/scsi/fcoe/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_FCOE) += fcoe.o +obj-$(CONFIG_LIBFCOE) += libfcoe.o + +libfcoe-objs := fcoe_ctlr.o fcoe_transport.o fcoe_sysfs.o diff --git a/drivers/scsi/fcoe/fcoe.c b/drivers/scsi/fcoe/fcoe.c new file mode 100644 index 000000000..30afcbbe1 --- /dev/null +++ b/drivers/scsi/fcoe/fcoe.c @@ -0,0 +1,2837 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2007 - 2009 Intel Corporation. All rights reserved. + * + * Maintained at www.Open-FCoE.org + */ + +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#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> +#include <scsi/scsi_transport_fc.h> +#include <net/rtnetlink.h> + +#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> +#include <scsi/libfcoe.h> + +#include "fcoe.h" + +MODULE_AUTHOR("Open-FCoE.org"); +MODULE_DESCRIPTION("FCoE"); +MODULE_LICENSE("GPL v2"); + +/* Performance tuning parameters for fcoe */ +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)."); + +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 unsigned int fcoe_e_d_tov = 2 * 1000; +module_param_named(e_d_tov, fcoe_e_d_tov, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(e_d_tov, "E_D_TOV in ms, default 2000"); + +static unsigned int fcoe_r_a_tov = 2 * 2 * 1000; +module_param_named(r_a_tov, fcoe_r_a_tov, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(r_a_tov, "R_A_TOV in ms, default 4000"); + +static DEFINE_MUTEX(fcoe_config_mutex); + +static struct workqueue_struct *fcoe_wq; + +/* fcoe host list */ +/* must only by accessed under the RTNL mutex */ +static LIST_HEAD(fcoe_hostlist); +static DEFINE_PER_CPU(struct fcoe_percpu_s, fcoe_percpu); + +/* Function Prototypes */ +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 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 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 *); + +static int fcoe_fip_recv(struct sk_buff *, struct net_device *, + struct packet_type *, struct net_device *); +static int fcoe_fip_vlan_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_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_mode 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 void fcoe_ctlr_mode(struct fcoe_ctlr_device *ctlr_dev); + +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, +}; + +/* 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 void fcoe_vport_remove(struct fc_lport *); + +static struct fcoe_sysfs_function_template fcoe_sysfs_templ = { + .set_fcoe_ctlr_mode = fcoe_ctlr_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, +}; + +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, + .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, + + .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 = { + .module = THIS_MODULE, + .name = "FCoE Driver", + .proc_name = FCOE_NAME, + .queuecommand = fc_queuecommand, + .eh_timed_out = fc_eh_timed_out, + .eh_abort_handler = fc_eh_abort, + .eh_device_reset_handler = fc_eh_device_reset, + .eh_host_reset_handler = fc_eh_host_reset, + .slave_alloc = fc_slave_alloc, + .change_queue_depth = scsi_change_queue_depth, + .this_id = -1, + .cmd_per_lun = 3, + .can_queue = FCOE_MAX_OUTSTANDING_COMMANDS, + .sg_tablesize = SG_ALL, + .max_sectors = 0xffff, + .track_queue_depth = 1, +}; + +/** + * 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 + */ +static int fcoe_interface_setup(struct fcoe_interface *fcoe, + struct net_device *netdev) +{ + 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_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 = is_vlan_dev(netdev) ? vlan_dev_real_dev(netdev) : netdev; + fcoe->realdev = real_dev; + rcu_read_lock(); + for_each_dev_addr(real_dev, ha) { + if ((ha->type == NETDEV_HW_ADDR_T_SAN) && + (is_valid_ether_addr(ha->addr))) { + memcpy(fip->ctl_src_addr, ha->addr, ETH_ALEN); + fip->spma = 1; + break; + } + } + rcu_read_unlock(); + + /* setup Source Mac Address */ + if (!fip->spma) + memcpy(fip->ctl_src_addr, netdev->dev_addr, netdev->addr_len); + + /* + * Add FCoE MAC address as second unicast MAC address + * or enter promiscuous mode if not capable of listening + * for multiple unicast MACs. + */ + memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN); + dev_uc_add(netdev, flogi_maddr); + if (fip->spma) + 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 + * on the ethertype for the given device + */ + fcoe->fcoe_packet_type.func = fcoe_rcv; + fcoe->fcoe_packet_type.type = htons(ETH_P_FCOE); + fcoe->fcoe_packet_type.dev = netdev; + dev_add_pack(&fcoe->fcoe_packet_type); + + fcoe->fip_packet_type.func = fcoe_fip_recv; + fcoe->fip_packet_type.type = htons(ETH_P_FIP); + fcoe->fip_packet_type.dev = netdev; + dev_add_pack(&fcoe->fip_packet_type); + + if (netdev != real_dev) { + fcoe->fip_vlan_packet_type.func = fcoe_fip_vlan_recv; + fcoe->fip_vlan_packet_type.type = htons(ETH_P_FIP); + fcoe->fip_vlan_packet_type.dev = real_dev; + dev_add_pack(&fcoe->fip_vlan_packet_type); + } + return 0; +} + +/** + * 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, + enum fip_mode fip_mode) +{ + struct fcoe_ctlr_device *ctlr_dev; + struct fcoe_ctlr *ctlr; + struct fcoe_interface *fcoe; + int size; + int err; + + 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); + + /* + * Initialize FIP. + */ + 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; + } + + goto out; + +out_putmod: + module_put(THIS_MODULE); +out: + return fcoe; +} + +/** + * fcoe_interface_remove() - remove FCoE interface from netdev + * @fcoe: The FCoE interface to be cleaned up + * + * Caller must be holding the RTNL mutex + */ +static void fcoe_interface_remove(struct fcoe_interface *fcoe) +{ + struct net_device *netdev = fcoe->netdev; + 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. + * synchronize_net() ensures that the packet handlers are not running + * on another CPU. dev_remove_pack() would do that, this calls the + * unsyncronized version __dev_remove_pack() to avoid multiple delays. + */ + __dev_remove_pack(&fcoe->fcoe_packet_type); + __dev_remove_pack(&fcoe->fip_packet_type); + if (netdev != fcoe->realdev) + __dev_remove_pack(&fcoe->fip_vlan_packet_type); + synchronize_net(); + + /* Delete secondary MAC addresses */ + memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN); + dev_uc_del(netdev, flogi_maddr); + if (fip->spma) + 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_cleanup() - Clean up a FCoE interface + * @fcoe: The FCoE interface to be cleaned up + */ +static void fcoe_interface_cleanup(struct fcoe_interface *fcoe) +{ + struct net_device *netdev = fcoe->netdev; + struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe); + + /* Release the self-reference taken during fcoe_interface_create() */ + /* tear-down the FCoE controller */ + fcoe_ctlr_destroy(fip); + scsi_host_put(fip->lp->host); + dev_put(netdev); + module_put(THIS_MODULE); +} + +/** + * 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 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 *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); + ctlr = fcoe_to_ctlr(fcoe); + fcoe_ctlr_recv(ctlr, skb); + return 0; +} + +/** + * fcoe_fip_vlan_recv() - Handler for received FIP VLAN discovery 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 skb was received on. + * (in case dev is a bond) + * + * Returns: 0 for success + */ +static int fcoe_fip_vlan_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_vlan_packet_type); + ctlr = fcoe_to_ctlr(fcoe); + fcoe_ctlr_recv(ctlr, skb); + return 0; +} + +/** + * 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) +{ + struct fcoe_interface *fcoe = fcoe_from_ctlr(fip); + struct fip_frame { + struct ethhdr eth; + struct fip_header fip; + } __packed *frame; + + /* + * Use default VLAN for FIP VLAN discovery protocol + */ + frame = (struct fip_frame *)skb->data; + if (ntohs(frame->eth.h_proto) == ETH_P_FIP && + ntohs(frame->fip.fip_op) == FIP_OP_VLAN && + fcoe->realdev != fcoe->netdev) + skb->dev = fcoe->realdev; + else + skb->dev = fcoe->netdev; + fcoe_port_send(lport_priv(fip->lp), skb); +} + +/** + * 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 fc_lport *lport, u8 *addr) +{ + struct fcoe_port *port = lport_priv(lport); + struct fcoe_interface *fcoe = port->priv; + + 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() - Set up a local port + * @lport: The local port to be setup + * + * Returns: 0 for success + */ +static int fcoe_lport_config(struct fc_lport *lport) +{ + lport->link_up = 0; + lport->qfull = 0; + lport->max_retry_count = 3; + lport->max_rport_retry_count = 3; + lport->e_d_tov = fcoe_e_d_tov; + lport->r_a_tov = fcoe_r_a_tov; + 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(lport); + + /* offload related configuration */ + lport->crc_offload = 0; + lport->seq_offload = 0; + lport->lro_enabled = 0; + lport->lro_xid = 0; + lport->lso_max = 0; + + return 0; +} + +/* + * fcoe_netdev_features_change - Updates the lport's offload flags based + * on the LLD netdev's FCoE feature flags + */ +static void fcoe_netdev_features_change(struct fc_lport *lport, + struct net_device *netdev) +{ + 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 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 local port mutex + * + * Returns: 0 for success + */ +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(lport); + fcoe = port->priv; + ctlr = fcoe_to_ctlr(fcoe); + + /* Figure out the VLAN ID, if any */ + if (is_vlan_dev(netdev)) + lport->vlan = vlan_dev_vlan_id(netdev); + else + lport->vlan = 0; + + /* + * 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; + 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 */ + fcoe_netdev_features_change(lport, netdev); + + skb_queue_head_init(&port->fcoe_pending_queue); + port->fcoe_pending_queue_active = 0; + timer_setup(&port->timer, fcoe_queue_timer, 0); + + 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() - 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 + */ +static int fcoe_shost_config(struct fc_lport *lport, struct device *dev) +{ + int rc = 0; + + /* lport scsi host config */ + 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; + + 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(lport->host, dev); + if (rc) { + FCOE_NETDEV_DBG(fcoe_netdev(lport), "fcoe_shost_config: " + "error on scsi_add_host\n"); + return rc; + } + + 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_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 + * + */ +static void fcoe_fdmi_info(struct fc_lport *lport, struct net_device *netdev) +{ + struct fcoe_interface *fcoe; + struct fcoe_port *port; + struct net_device *realdev; + int rc; + + port = lport_priv(lport); + fcoe = port->priv; + realdev = fcoe->realdev; + + /* 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_oem_match() - The match routine for the offloaded exchange manager + * @fp: The I/O frame + * + * 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 bool fcoe_oem_match(struct fc_frame *fp) +{ + 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; + u16 max_xid = FCOE_MAX_XID; + + /* + * Check if need to allocate an em instance for + * offload exchange ids to be shared across all VN_PORTs/lport. + */ + if (!lport->lro_enabled || !lport->lro_xid || + (lport->lro_xid >= max_xid)) { + lport->lro_xid = 0; + goto skip_oem; + } + + /* + * Reuse existing offload em instance in case + * it is already allocated on real eth device + */ + if (is_vlan_dev(fcoe->netdev)) + cur_real_dev = vlan_dev_real_dev(fcoe->netdev); + else + cur_real_dev = fcoe->netdev; + + list_for_each_entry(oldfcoe, &fcoe_hostlist, list) { + if (is_vlan_dev(oldfcoe->netdev)) + old_real_dev = vlan_dev_real_dev(oldfcoe->netdev); + else + old_real_dev = oldfcoe->netdev; + + if (cur_real_dev == old_real_dev) { + fcoe->oem = oldfcoe->oem; + break; + } + } + + if (fcoe->oem) { + 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(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", + fcoe->netdev->name); + return -ENOMEM; + } + } + + /* + * Exclude offload EM xid range from next EM xid range. + */ + min_xid += lport->lro_xid + 1; + +skip_oem: + 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; + } + + return 0; +} + +/** + * fcoe_if_destroy() - Tear down a SW FCoE instance + * @lport: The local port to be destroyed + * + * Locking: Must be called with the RTNL mutex held. + * + */ +static void fcoe_if_destroy(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + struct fcoe_interface *fcoe = port->priv; + struct net_device *netdev = fcoe->netdev; + + FCOE_NETDEV_DBG(netdev, "Destroying interface\n"); + + /* Logout of the fabric */ + fc_fabric_logoff(lport); + + /* Cleanup the fc_lport */ + fc_lport_destroy(lport); + + /* Stop the transmit retry timer */ + del_timer_sync(&port->timer); + + /* Free existing transmit skbs */ + fcoe_clean_pending_queue(lport); + + 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); + + /* Free queued packets for the per-CPU receive threads */ + fcoe_percpu_clean(lport); + + /* Detach from the scsi-ml */ + 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 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() - 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 if the DDP context was not configured + */ +static int fcoe_ddp_setup(struct fc_lport *lport, u16 xid, + struct scatterlist *sgl, unsigned int sgc) +{ + struct net_device *netdev = fcoe_netdev(lport); + + if (netdev->netdev_ops->ndo_fcoe_ddp_setup) + return netdev->netdev_ops->ndo_fcoe_ddp_setup(netdev, + xid, sgl, + sgc); + + return 0; +} + +/** + * 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: 0 if the DDP context was not configured + */ +static int fcoe_ddp_target(struct fc_lport *lport, u16 xid, + struct scatterlist *sgl, unsigned int sgc) +{ + 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); + + return 0; +} + + +/** + * 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 + * + * 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 + */ +static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe, + struct device *parent, int npiv) +{ + 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; + 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"); + + 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; + } + port = lport_priv(lport); + port->lport = lport; + 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); + + /* + * 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 " + "interface\n"); + 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) { + FCOE_NETDEV_DBG(netdev, "Could not configure netdev for the " + "interface\n"); + goto out_lp_destroy; + } + + /* configure lport scsi host properties */ + rc = fcoe_shost_config(lport, parent); + if (rc) { + FCOE_NETDEV_DBG(netdev, "Could not configure shost for the " + "interface\n"); + goto out_lp_destroy; + } + + /* Initialize the library */ + 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 + * instance on host list updated by fcoe_hostlist_add(). + * + * 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); + } + + if (rc) { + FCOE_NETDEV_DBG(netdev, "Could not configure the EM\n"); + goto out_lp_destroy; + } + + 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() - Initialization routine for fcoe.ko + * + * Attaches the SW FCoE transport to the FC transport + * + * Returns: 0 on success + */ +static int __init fcoe_if_init(void) +{ + /* attach to scsi transport */ + fcoe_nport_scsi_transport = + fc_attach_transport(&fcoe_nport_fc_functions); + if (!fcoe_nport_scsi_transport) + goto err; + + fcoe_vport_scsi_transport = + fc_attach_transport(&fcoe_vport_fc_functions); + if (!fcoe_vport_scsi_transport) + goto err_vport; + + return 0; + +err_vport: + fc_release_transport(fcoe_nport_scsi_transport); +err: + printk(KERN_ERR "fcoe: Failed to attach to the FC transport\n"); + return -ENODEV; +} + +/** + * fcoe_if_exit() - Tear down fcoe.ko + * + * Detaches the SW FCoE transport from the FC transport + * + * Returns: 0 on success + */ +static int __exit fcoe_if_exit(void) +{ + 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; +} + +static void fcoe_thread_cleanup_local(unsigned int cpu) +{ + struct page *crc_eof; + struct fcoe_percpu_s *p; + + p = per_cpu_ptr(&fcoe_percpu, cpu); + spin_lock_bh(&p->fcoe_rx_list.lock); + crc_eof = p->crc_eof_page; + p->crc_eof_page = NULL; + p->crc_eof_offset = 0; + spin_unlock_bh(&p->fcoe_rx_list.lock); + + if (crc_eof) + put_page(crc_eof); + flush_work(&p->work); +} + +/** + * 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() - 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 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 + */ +static int fcoe_rcv(struct sk_buff *skb, struct net_device *netdev, + struct packet_type *ptype, struct net_device *olddev) +{ + 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); + ctlr = fcoe_to_ctlr(fcoe); + lport = ctlr->lp; + if (unlikely(!lport)) { + FCOE_NETDEV_DBG(netdev, "Cannot find hba structure\n"); + goto err2; + } + if (!lport->link_up) + goto err2; + + 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>"); + + + 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; + } + + /* + * Check for minimum frame length, and make sure required FCoE + * and FC headers are pulled into the linear data area. + */ + if (unlikely((skb->len < FCOE_MIN_FRAME) || + !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 = 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 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 { + 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(&fps->fcoe_rx_list.lock); + /* + * We now have a valid CPU that we're targeting for + * 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); + schedule_work_on(cpu, &fps->work); + spin_unlock(&fps->fcoe_rx_list.lock); + + return NET_RX_SUCCESS; +err: + per_cpu_ptr(lport->stats, get_cpu())->ErrorFrames++; + put_cpu(); +err2: + kfree_skb(skb); + return NET_RX_DROP; +} + +/** + * 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_alloc_paged_crc_eof(struct sk_buff *skb, int tlen) +{ + struct fcoe_percpu_s *fps; + int rc; + + fps = &get_cpu_var(fcoe_percpu); + rc = fcoe_get_paged_crc_eof(skb, tlen, fps); + put_cpu_var(fcoe_percpu); + + return rc; +} + +/** + * 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 + */ +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 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(lport); + struct fcoe_interface *fcoe = port->priv; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); + u8 sof, eof; + struct fcoe_hdr *hp; + + WARN_ON((fr_len(fp) % sizeof(u32)) != 0); + + fh = fc_frame_header_get(fp); + skb = fp_skb(fp); + wlen = skb->len / FCOE_WORD_TO_BYTE; + + if (!lport->link_up) { + kfree_skb(skb); + return 0; + } + + if (unlikely(fh->fh_type == FC_TYPE_ELS) && + fcoe_ctlr_els_send(ctlr, lport, skb)) + return 0; + + sof = fr_sof(fp); + eof = fr_eof(fp); + + elen = sizeof(struct ethhdr); + hlen = sizeof(struct fcoe_hdr); + tlen = sizeof(struct fcoe_crc_eof); + wlen = (skb->len - tlen + sizeof(crc)) / FCOE_WORD_TO_BYTE; + + /* crc offload */ + if (likely(lport->crc_offload)) { + skb->ip_summed = CHECKSUM_PARTIAL; + skb->csum_start = skb_headroom(skb); + skb->csum_offset = skb->len; + crc = 0; + } else { + skb->ip_summed = CHECKSUM_NONE; + crc = fcoe_fc_crc(fp); + } + + /* copy port crc and eof to the skb buff */ + if (skb_is_nonlinear(skb)) { + skb_frag_t *frag; + 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(skb_frag_page(frag)) + skb_frag_off(frag); + } else { + cp = skb_put(skb, tlen); + } + + memset(cp, 0, sizeof(*cp)); + cp->fcoe_eof = eof; + cp->fcoe_crc32 = cpu_to_le32(~crc); + + if (skb_is_nonlinear(skb)) { + kunmap_atomic(cp); + cp = NULL; + } + + /* adjust skb network/transport offsets to match mac/fcoe/port */ + skb_push(skb, elen + hlen); + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb->mac_len = elen; + skb->protocol = htons(ETH_P_FCOE); + skb->priority = fcoe->priority; + + if (is_vlan_dev(fcoe->netdev) && + fcoe->realdev->features & NETIF_F_HW_VLAN_CTAG_TX) { + /* must set skb->dev before calling vlan_put_tag */ + skb->dev = fcoe->realdev; + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), + vlan_dev_vlan_id(fcoe->netdev)); + } else + skb->dev = fcoe->netdev; + + /* fill up mac and fcoe headers */ + eh = eth_hdr(skb); + eh->h_proto = htons(ETH_P_FCOE); + 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(ctlr->flogi_oxid != FC_XID_UNKNOWN)) + memcpy(eh->h_source, ctlr->ctl_src_addr, ETH_ALEN); + else + memcpy(eh->h_source, port->data_src_addr, ETH_ALEN); + + hp = (struct fcoe_hdr *)(eh + 1); + memset(hp, 0, sizeof(*hp)); + if (FC_FCOE_VER) + FC_FCOE_ENCAPS_VER(hp, FC_FCOE_VER); + hp->fcoe_sof = sof; + + /* fcoe lso, mss is in max_payload which is non-zero for FCP data */ + 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 { + skb_shinfo(skb)->gso_type = 0; + skb_shinfo(skb)->gso_size = 0; + } + /* update tx stats: regardless if LLD fails */ + stats = per_cpu_ptr(lport->stats, get_cpu()); + stats->TxFrames++; + stats->TxWords += wlen; + put_cpu(); + + /* send down to lld */ + fr_dev(fp) = lport; + fcoe_port_send(port, skb); + return 0; +} + +/** + * 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 on passing filtering checks + */ +static inline int fcoe_filter_frames(struct fc_lport *lport, + struct fc_frame *fp) +{ + struct fcoe_ctlr *ctlr; + struct fcoe_interface *fcoe; + struct fc_frame_header *fh; + struct sk_buff *skb = (struct sk_buff *)fp; + struct fc_stats *stats; + + /* + * 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; + + 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 = ((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; + } + + 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; + } + + 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; +} + +/** + * 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_hdr *hp; + + fr = fcoe_dev_from_skb(skb); + lport = fr->fr_dev; + if (unlikely(!lport)) { + FCOE_NETDEV_DBG(skb->dev, "NULL lport in skb\n"); + kfree_skb(skb); + return; + } + + 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>"); + + skb_linearize(skb); /* check for skb_is_nonlinear is within skb_linearize */ + + /* + * 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; + } + + 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; + } +drop: + stats->ErrorFrames++; + put_cpu(); + kfree_skb(skb); +} + +/** + * fcoe_receive_work() - The per-CPU worker + * @work: The work struct + * + */ +static void fcoe_receive_work(struct work_struct *work) +{ + struct fcoe_percpu_s *p; + struct sk_buff *skb; + struct sk_buff_head tmp; + + p = container_of(work, struct fcoe_percpu_s, work); + skb_queue_head_init(&tmp); + + spin_lock_bh(&p->fcoe_rx_list.lock); + skb_queue_splice_init(&p->fcoe_rx_list, &tmp); + spin_unlock_bh(&p->fcoe_rx_list.lock); + + if (!skb_queue_len(&tmp)) + return; + + while ((skb = __skb_dequeue(&tmp))) + fcoe_recv_frame(skb); +} + +/** + * 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 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 (is_vlan_dev(fcoe->netdev)) + 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() - 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. + * + * Returns: 0 for success + */ +static int fcoe_device_notification(struct notifier_block *notifier, + ulong event, void *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 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) { + ctlr = fcoe_to_ctlr(fcoe); + lport = ctlr->lp; + break; + } + } + if (!lport) { + rc = NOTIFY_DONE; + goto out; + } + + switch (event) { + case NETDEV_DOWN: + case NETDEV_GOING_DOWN: + link_possible = 0; + break; + case NETDEV_UP: + 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(lport, mfs); + break; + case NETDEV_REGISTER: + break; + case NETDEV_UNREGISTER: + list_del(&fcoe->list); + fcoe_vport_remove(lport); + mutex_lock(&fcoe_config_mutex); + fcoe_if_destroy(lport); + if (!fcoe->removed) + fcoe_interface_remove(fcoe); + fcoe_interface_cleanup(fcoe); + mutex_unlock(&fcoe_config_mutex); + fcoe_ctlr_device_delete(fcoe_ctlr_to_ctlr_dev(ctlr)); + 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); + } + + 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_disable() - Disables a FCoE interface + * @netdev : The net_device object the Ethernet interface to create on + * + * Called from fcoe transport. + * + * Returns: 0 for success + * + * Deprecated: use fcoe_ctlr_enabled() + */ +static int fcoe_disable(struct net_device *netdev) +{ + struct fcoe_ctlr *ctlr; + struct fcoe_interface *fcoe; + int rc = 0; + + 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_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_enable(struct net_device *netdev) +{ + struct fcoe_ctlr *ctlr; + struct fcoe_interface *fcoe; + int rc = 0; + + mutex_lock(&fcoe_config_mutex); + rtnl_lock(); + fcoe = fcoe_hostlist_lookup_port(netdev); + rtnl_unlock(); + + if (!fcoe) { + rc = -ENODEV; + 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_ctlr_mode() - Switch FIP mode + * @ctlr_dev: The FCoE Controller that is being modified + * + * When the FIP mode has been changed we need to update + * the multicast addresses to ensure we get the correct + * frames. + */ +static void fcoe_ctlr_mode(struct fcoe_ctlr_device *ctlr_dev) +{ + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + struct fcoe_interface *fcoe = fcoe_ctlr_priv(ctlr); + + if (ctlr_dev->mode == FIP_CONN_TYPE_VN2VN && + ctlr->mode != FIP_MODE_VN2VN) { + dev_mc_del(fcoe->netdev, FIP_ALL_ENODE_MACS); + dev_mc_add(fcoe->netdev, FIP_ALL_VN2VN_MACS); + dev_mc_add(fcoe->netdev, FIP_ALL_P2P_MACS); + } else if (ctlr->mode != FIP_MODE_FABRIC) { + dev_mc_del(fcoe->netdev, FIP_ALL_VN2VN_MACS); + dev_mc_del(fcoe->netdev, FIP_ALL_P2P_MACS); + dev_mc_add(fcoe->netdev, FIP_ALL_ENODE_MACS); + } + fcoe_ctlr_set_fip_mode(ctlr_dev); +} + +/** + * 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) { + rc = -ENODEV; + goto out_nodev; + } + ctlr = fcoe_to_ctlr(fcoe); + lport = ctlr->lp; + port = lport_priv(lport); + list_del(&fcoe->list); + 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; + + port = container_of(work, struct fcoe_port, destroy_work); + + fcoe_vport_remove(port->lport); + + mutex_lock(&fcoe_config_mutex); + + fcoe = port->priv; + ctlr = fcoe_to_ctlr(fcoe); + cdev = fcoe_ctlr_to_ctlr_dev(ctlr); + + rtnl_lock(); + fcoe_if_destroy(port->lport); + if (!fcoe->removed) + fcoe_interface_remove(fcoe); + rtnl_unlock(); + fcoe_interface_cleanup(fcoe); + + mutex_unlock(&fcoe_config_mutex); + + fcoe_ctlr_device_delete(cdev); +} + +/** + * fcoe_match() - Check if the FCoE is supported on the given netdevice + * @netdev : The net_device object the Ethernet interface to create on + * + * Called from fcoe transport. + * + * Returns: always returns true as this is the default FCoE transport, + * i.e., support all netdevs. + */ +static bool fcoe_match(struct net_device *netdev) +{ + return true; +} + +/** + * fcoe_dcb_create() - Initialize DCB attributes and hooks + * @fcoe: The new FCoE interface + */ +static void fcoe_dcb_create(struct fcoe_interface *fcoe) +{ + int ctlr_prio = TC_PRIO_BESTEFFORT; + int fcoe_prio = TC_PRIO_INTERACTIVE; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); +#ifdef CONFIG_DCB + int dcbx; + u8 fup, up; + struct net_device *netdev = fcoe->realdev; + 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_prio = ffs(up) ? ffs(up) - 1 : 0; + ctlr_prio = ffs(fup) ? ffs(fup) - 1 : fcoe_prio; + } +#endif + fcoe->priority = fcoe_prio; + ctlr->priority = ctlr_prio; +} + +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_mode 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(); + + /* look for existing lport */ + if (fcoe_hostlist_lookup(netdev)) { + rc = -EEXIST; + goto out_nodev; + } + + fcoe = fcoe_interface_create(netdev, fip_mode); + if (IS_ERR(fcoe)) { + rc = PTR_ERR(fcoe); + goto out_nodev; + } + + 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; + if (!fcoe->removed) + fcoe_interface_remove(fcoe); + rtnl_unlock(); + fcoe_interface_cleanup(fcoe); + mutex_unlock(&fcoe_config_mutex); + fcoe_ctlr_device_delete(ctlr_dev); + return rc; + } + + /* Make this the "master" N_Port */ + ctlr->lp = lport; + + /* setup DCB priority attributes. */ + fcoe_dcb_create(fcoe); + + /* start FIP Discovery and FLOGI */ + lport->boot_time = jiffies; + fc_fabric_login(lport); + + /* + * 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. + */ + 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); + + return rc; +} + +/** + * 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 + * + * Called from fcoe transport + * + * Returns: 0 for success + */ +static int fcoe_create(struct net_device *netdev, enum fip_mode 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 + * + * 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. + */ +static int fcoe_ctlr_alloc(struct net_device *netdev) +{ + return _fcoe_create(netdev, FIP_MODE_FABRIC, + FCOE_CREATE_LINK_DOWN); +} + +/** + * 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 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. + * + * This flushes the pending skbs by flush the work item for each CPU. The work + * item on each possible CPU is flushed because we may have used the per-CPU + * struct of an offline CPU. + */ +static void fcoe_percpu_clean(struct fc_lport *lport) +{ + struct fcoe_percpu_s *pp; + unsigned int cpu; + + for_each_possible_cpu(cpu) { + pp = &per_cpu(fcoe_percpu, cpu); + + flush_work(&pp->work); + } +} + +/** + * fcoe_reset() - Reset a local port + * @shost: The SCSI host associated with the local port to be reset + * + * Returns: Always 0 (return value required by FC transport template) + */ +static int fcoe_reset(struct Scsi_Host *shost) +{ + 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); + + fcoe_ctlr_link_down(ctlr); + fcoe_clean_pending_queue(ctlr->lp); + + if (cdev->enabled != FCOE_CTLR_DISABLED && + !fcoe_link_ok(ctlr->lp)) + fcoe_ctlr_link_up(ctlr); + return 0; +} + +/** + * fcoe_hostlist_lookup_port() - Find the FCoE interface associated with a net device + * @netdev: The net device used as a key + * + * 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 *netdev) +{ + struct fcoe_interface *fcoe; + + list_for_each_entry(fcoe, &fcoe_hostlist, list) { + if (fcoe->netdev == netdev) + return fcoe; + } + return NULL; +} + +/** + * fcoe_hostlist_lookup() - Find the local port associated with a + * given net device + * @netdev: The netdevice used as a key + * + * 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); + ctlr = fcoe_to_ctlr(fcoe); + return (fcoe) ? ctlr->lp : NULL; +} + +/** + * 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 + * + * Locking: must be called with the RTNL mutex held + * + * Returns: 0 for success + */ +static int fcoe_hostlist_add(const struct fc_lport *lport) +{ + struct fcoe_interface *fcoe; + struct fcoe_port *port; + + fcoe = fcoe_hostlist_lookup_port(fcoe_netdev(lport)); + if (!fcoe) { + port = lport_priv(lport); + fcoe = port->priv; + list_add_tail(&fcoe->list, &fcoe_hostlist); + } + return 0; +} + +/** + * 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 + * + */ +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; + + 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"); + goto out_destroy; + } + + mutex_lock(&fcoe_config_mutex); + + for_each_possible_cpu(cpu) { + p = per_cpu_ptr(&fcoe_percpu, cpu); + INIT_WORK(&p->work, fcoe_receive_work); + skb_queue_head_init(&p->fcoe_rx_list); + } + + /* Setup link change notification */ + fcoe_dev_setup(); + + rc = fcoe_if_init(); + if (rc) + goto out_free; + + mutex_unlock(&fcoe_config_mutex); + return 0; + +out_free: + mutex_unlock(&fcoe_config_mutex); + fcoe_transport_detach(&fcoe_sw_transport); +out_destroy: + destroy_workqueue(fcoe_wq); + return rc; +} +module_init(fcoe_init); + +/** + * fcoe_exit() - Clean up fcoe.ko + * + * Returns: 0 on success or a negative value on failure + */ +static void __exit fcoe_exit(void) +{ + struct fcoe_interface *fcoe, *tmp; + struct fcoe_ctlr *ctlr; + struct fcoe_port *port; + unsigned int cpu; + + mutex_lock(&fcoe_config_mutex); + + fcoe_dev_cleanup(); + + /* releases the associated fcoe hosts */ + rtnl_lock(); + list_for_each_entry_safe(fcoe, tmp, &fcoe_hostlist, list) { + ctlr = fcoe_to_ctlr(fcoe); + port = lport_priv(ctlr->lp); + fcoe_hostlist_del(port->lport); + queue_work(fcoe_wq, &port->destroy_work); + } + rtnl_unlock(); + + for_each_possible_cpu(cpu) + fcoe_thread_cleanup_local(cpu); + + mutex_unlock(&fcoe_config_mutex); + + /* + * destroy_work's may be chained but destroy_workqueue() + * can take care of them. Just kill the fcoe_wq. + */ + destroy_workqueue(fcoe_wq); + + /* + * 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 to 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 to 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); + rtnl_lock(); + fcoe_if_destroy(vn_port); + rtnl_unlock(); + mutex_unlock(&fcoe_config_mutex); + + return 0; +} + +/** + * fcoe_vport_remove() - remove attached vports + * @lport: lport for which the vports should be removed + */ +static void fcoe_vport_remove(struct fc_lport *lport) +{ + struct Scsi_Host *shost; + struct fc_host_attrs *fc_host; + unsigned long flags; + struct fc_vport *vport; + struct fc_vport *next_vport; + + shost = 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)); +} + +/** + * 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); +} diff --git a/drivers/scsi/fcoe/fcoe.h b/drivers/scsi/fcoe/fcoe.h new file mode 100644 index 000000000..520c53512 --- /dev/null +++ b/drivers/scsi/fcoe/fcoe.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2009 Intel Corporation. All rights reserved. + * + * Maintained at www.Open-FCoE.org + */ + +#ifndef _FCOE_H_ +#define _FCOE_H_ + +#include <linux/skbuff.h> +#include <linux/kthread.h> + +#define FCOE_MAX_QUEUE_DEPTH 256 +#define FCOE_MIN_QUEUE_DEPTH 32 + +#define FCOE_WORD_TO_BYTE 4 + +#define FCOE_VERSION "0.1" +#define FCOE_NAME "fcoe" +#define FCOE_VENDOR "Open-FCoE.org" + +#define FCOE_MAX_LUN 0xFFFF +#define FCOE_MAX_FCP_TARGET 256 + +#define FCOE_MAX_OUTSTANDING_COMMANDS 1024 + +#define FCOE_MIN_XID 0x0000 /* the min xid supported by fcoe_sw */ +#define FCOE_MAX_XID 0x0FFF /* the max xid supported by fcoe_sw */ + +extern unsigned int fcoe_debug_logging; + +#define FCOE_LOGGING 0x01 /* General logging, not categorized */ +#define FCOE_NETDEV_LOGGING 0x02 /* Netdevice logging */ + +#define FCOE_CHECK_LOGGING(LEVEL, CMD) \ +do { \ + if (unlikely(fcoe_debug_logging & LEVEL)) \ + do { \ + CMD; \ + } while (0); \ +} while (0) + +#define FCOE_DBG(fmt, args...) \ + FCOE_CHECK_LOGGING(FCOE_LOGGING, \ + pr_info("fcoe: " fmt, ##args);) + +#define FCOE_NETDEV_DBG(netdev, fmt, args...) \ + FCOE_CHECK_LOGGING(FCOE_NETDEV_LOGGING, \ + pr_info("fcoe: %s: " fmt, \ + netdev->name, ##args);) + +/** + * struct fcoe_interface - A FCoE interface + * @list: Handle for a list of FCoE interfaces + * @netdev: The associated net device + * @fcoe_packet_type: FCoE packet type + * @fip_packet_type: FIP packet type + * @oem: The offload exchange manager for all local port + * instances associated with this port + * @removed: Indicates fcoe interface removed from net device + * @priority: Priority for the FCoE packet (DCB) + * This structure is 1:1 with a net device. + */ +struct fcoe_interface { + struct list_head list; + struct net_device *netdev; + struct net_device *realdev; + struct packet_type fcoe_packet_type; + struct packet_type fip_packet_type; + struct packet_type fip_vlan_packet_type; + struct fc_exch_mgr *oem; + u8 removed; + u8 priority; +}; + +#define fcoe_to_ctlr(x) \ + (struct fcoe_ctlr *)(((struct fcoe_ctlr *)(x)) - 1) + +#define fcoe_from_ctlr(x) \ + ((struct fcoe_interface *)((x) + 1)) + +/** + * fcoe_netdev() - Return the net device associated with a local port + * @lport: The local port to get the net device from + */ +static inline struct net_device *fcoe_netdev(const struct fc_lport *lport) +{ + return ((struct fcoe_interface *) + ((struct fcoe_port *)lport_priv(lport))->priv)->netdev; +} + +#endif /* _FCOE_H_ */ diff --git a/drivers/scsi/fcoe/fcoe_ctlr.c b/drivers/scsi/fcoe/fcoe_ctlr.c new file mode 100644 index 000000000..a2d60ad2a --- /dev/null +++ b/drivers/scsi/fcoe/fcoe_ctlr.c @@ -0,0 +1,3266 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2008-2009 Cisco Systems, Inc. All rights reserved. + * Copyright (c) 2009 Intel Corporation. All rights reserved. + * + * Maintained at www.Open-FCoE.org + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/if_ether.h> +#include <linux/if_vlan.h> +#include <linux/errno.h> +#include <linux/bitops.h> +#include <linux/slab.h> +#include <net/rtnetlink.h> + +#include <scsi/fc/fc_els.h> +#include <scsi/fc/fc_fs.h> +#include <scsi/fc/fc_fip.h> +#include <scsi/fc/fc_encaps.h> +#include <scsi/fc/fc_fcoe.h> +#include <scsi/fc/fc_fcp.h> + +#include <scsi/libfc.h> +#include <scsi/libfcoe.h> + +#include "libfcoe.h" + +#define FCOE_CTLR_MIN_FKA 500 /* min keep alive (mS) */ +#define FCOE_CTLR_DEF_FKA FIP_DEF_FKA /* default keep alive (mS) */ + +static void fcoe_ctlr_timeout(struct timer_list *); +static void fcoe_ctlr_timer_work(struct work_struct *); +static void fcoe_ctlr_recv_work(struct work_struct *); +static int fcoe_ctlr_flogi_retry(struct fcoe_ctlr *); + +static void fcoe_ctlr_vn_start(struct fcoe_ctlr *); +static int fcoe_ctlr_vn_recv(struct fcoe_ctlr *, struct sk_buff *); +static void fcoe_ctlr_vn_timeout(struct fcoe_ctlr *); +static int fcoe_ctlr_vn_lookup(struct fcoe_ctlr *, u32, u8 *); + +static int fcoe_ctlr_vlan_recv(struct fcoe_ctlr *, struct sk_buff *); + +static u8 fcoe_all_fcfs[ETH_ALEN] = FIP_ALL_FCF_MACS; +static u8 fcoe_all_enode[ETH_ALEN] = FIP_ALL_ENODE_MACS; +static u8 fcoe_all_vn2vn[ETH_ALEN] = FIP_ALL_VN2VN_MACS; +static u8 fcoe_all_p2p[ETH_ALEN] = FIP_ALL_P2P_MACS; + +static const char * const fcoe_ctlr_states[] = { + [FIP_ST_DISABLED] = "DISABLED", + [FIP_ST_LINK_WAIT] = "LINK_WAIT", + [FIP_ST_AUTO] = "AUTO", + [FIP_ST_NON_FIP] = "NON_FIP", + [FIP_ST_ENABLED] = "ENABLED", + [FIP_ST_VNMP_START] = "VNMP_START", + [FIP_ST_VNMP_PROBE1] = "VNMP_PROBE1", + [FIP_ST_VNMP_PROBE2] = "VNMP_PROBE2", + [FIP_ST_VNMP_CLAIM] = "VNMP_CLAIM", + [FIP_ST_VNMP_UP] = "VNMP_UP", +}; + +static const char *fcoe_ctlr_state(enum fip_state state) +{ + const char *cp = "unknown"; + + if (state < ARRAY_SIZE(fcoe_ctlr_states)) + cp = fcoe_ctlr_states[state]; + if (!cp) + cp = "unknown"; + return cp; +} + +/** + * fcoe_ctlr_set_state() - Set and do debug printing for the new FIP state. + * @fip: The FCoE controller + * @state: The new state + */ +static void fcoe_ctlr_set_state(struct fcoe_ctlr *fip, enum fip_state state) +{ + if (state == fip->state) + return; + if (fip->lp) + LIBFCOE_FIP_DBG(fip, "state %s -> %s\n", + fcoe_ctlr_state(fip->state), fcoe_ctlr_state(state)); + fip->state = state; +} + +/** + * fcoe_ctlr_mtu_valid() - Check if a FCF's MTU is valid + * @fcf: The FCF to check + * + * Return non-zero if FCF fcoe_size has been validated. + */ +static inline int fcoe_ctlr_mtu_valid(const struct fcoe_fcf *fcf) +{ + return (fcf->flags & FIP_FL_SOL) != 0; +} + +/** + * fcoe_ctlr_fcf_usable() - Check if a FCF is usable + * @fcf: The FCF to check + * + * Return non-zero if the FCF is usable. + */ +static inline int fcoe_ctlr_fcf_usable(struct fcoe_fcf *fcf) +{ + u16 flags = FIP_FL_SOL | FIP_FL_AVAIL; + + return (fcf->flags & flags) == flags; +} + +/** + * fcoe_ctlr_map_dest() - Set flag and OUI for mapping destination addresses + * @fip: The FCoE controller + */ +static void fcoe_ctlr_map_dest(struct fcoe_ctlr *fip) +{ + if (fip->mode == FIP_MODE_VN2VN) + hton24(fip->dest_addr, FIP_VN_FC_MAP); + else + hton24(fip->dest_addr, FIP_DEF_FC_MAP); + hton24(fip->dest_addr + 3, 0); + fip->map_dest = 1; +} + +/** + * fcoe_ctlr_init() - Initialize the FCoE Controller instance + * @fip: The FCoE controller to initialize + * @mode: FIP mode to set + */ +void fcoe_ctlr_init(struct fcoe_ctlr *fip, enum fip_mode mode) +{ + fcoe_ctlr_set_state(fip, FIP_ST_LINK_WAIT); + fip->mode = mode; + fip->fip_resp = false; + INIT_LIST_HEAD(&fip->fcfs); + mutex_init(&fip->ctlr_mutex); + spin_lock_init(&fip->ctlr_lock); + fip->flogi_oxid = FC_XID_UNKNOWN; + timer_setup(&fip->timer, fcoe_ctlr_timeout, 0); + INIT_WORK(&fip->timer_work, fcoe_ctlr_timer_work); + INIT_WORK(&fip->recv_work, fcoe_ctlr_recv_work); + skb_queue_head_init(&fip->fip_recv_list); +} +EXPORT_SYMBOL(fcoe_ctlr_init); + +/** + * fcoe_sysfs_fcf_add() - Add a fcoe_fcf{,_device} to a fcoe_ctlr{,_device} + * @new: The newly discovered FCF + * + * Called with fip->ctlr_mutex held + */ +static int fcoe_sysfs_fcf_add(struct fcoe_fcf *new) +{ + struct fcoe_ctlr *fip = new->fip; + struct fcoe_ctlr_device *ctlr_dev; + struct fcoe_fcf_device *temp, *fcf_dev; + int rc = -ENOMEM; + + LIBFCOE_FIP_DBG(fip, "New FCF fab %16.16llx mac %pM\n", + new->fabric_name, new->fcf_mac); + + temp = kzalloc(sizeof(*temp), GFP_KERNEL); + if (!temp) + goto out; + + temp->fabric_name = new->fabric_name; + temp->switch_name = new->switch_name; + temp->fc_map = new->fc_map; + temp->vfid = new->vfid; + memcpy(temp->mac, new->fcf_mac, ETH_ALEN); + temp->priority = new->pri; + temp->fka_period = new->fka_period; + temp->selected = 0; /* default to unselected */ + + /* + * If ctlr_dev doesn't exist then it means we're a libfcoe user + * who doesn't use fcoe_syfs and didn't allocate a fcoe_ctlr_device. + * fnic would be an example of a driver with this behavior. In this + * case we want to add the fcoe_fcf to the fcoe_ctlr list, but we + * don't want to make sysfs changes. + */ + + ctlr_dev = fcoe_ctlr_to_ctlr_dev(fip); + if (ctlr_dev) { + mutex_lock(&ctlr_dev->lock); + fcf_dev = fcoe_fcf_device_add(ctlr_dev, temp); + if (unlikely(!fcf_dev)) { + rc = -ENOMEM; + mutex_unlock(&ctlr_dev->lock); + goto out; + } + + /* + * The fcoe_sysfs layer can return a CONNECTED fcf that + * has a priv (fcf was never deleted) or a CONNECTED fcf + * that doesn't have a priv (fcf was deleted). However, + * libfcoe will always delete FCFs before trying to add + * them. This is ensured because both recv_adv and + * age_fcfs are protected by the the fcoe_ctlr's mutex. + * This means that we should never get a FCF with a + * non-NULL priv pointer. + */ + BUG_ON(fcf_dev->priv); + + fcf_dev->priv = new; + new->fcf_dev = fcf_dev; + mutex_unlock(&ctlr_dev->lock); + } + + list_add(&new->list, &fip->fcfs); + fip->fcf_count++; + rc = 0; + +out: + kfree(temp); + return rc; +} + +/** + * fcoe_sysfs_fcf_del() - Remove a fcoe_fcf{,_device} to a fcoe_ctlr{,_device} + * @new: The FCF to be removed + * + * Called with fip->ctlr_mutex held + */ +static void fcoe_sysfs_fcf_del(struct fcoe_fcf *new) +{ + struct fcoe_ctlr *fip = new->fip; + struct fcoe_ctlr_device *cdev; + struct fcoe_fcf_device *fcf_dev; + + list_del(&new->list); + fip->fcf_count--; + + /* + * If ctlr_dev doesn't exist then it means we're a libfcoe user + * who doesn't use fcoe_syfs and didn't allocate a fcoe_ctlr_device + * or a fcoe_fcf_device. + * + * fnic would be an example of a driver with this behavior. In this + * case we want to remove the fcoe_fcf from the fcoe_ctlr list (above), + * but we don't want to make sysfs changes. + */ + cdev = fcoe_ctlr_to_ctlr_dev(fip); + if (cdev) { + mutex_lock(&cdev->lock); + fcf_dev = fcoe_fcf_to_fcf_dev(new); + WARN_ON(!fcf_dev); + new->fcf_dev = NULL; + fcoe_fcf_device_delete(fcf_dev); + mutex_unlock(&cdev->lock); + } + kfree(new); +} + +/** + * fcoe_ctlr_reset_fcfs() - Reset and free all FCFs for a controller + * @fip: The FCoE controller whose FCFs are to be reset + * + * Called with &fcoe_ctlr lock held. + */ +static void fcoe_ctlr_reset_fcfs(struct fcoe_ctlr *fip) +{ + struct fcoe_fcf *fcf; + struct fcoe_fcf *next; + + fip->sel_fcf = NULL; + list_for_each_entry_safe(fcf, next, &fip->fcfs, list) { + fcoe_sysfs_fcf_del(fcf); + } + WARN_ON(fip->fcf_count); + + fip->sel_time = 0; +} + +/** + * fcoe_ctlr_destroy() - Disable and tear down a FCoE controller + * @fip: The FCoE controller to tear down + * + * This is called by FCoE drivers before freeing the &fcoe_ctlr. + * + * The receive handler will have been deleted before this to guarantee + * that no more recv_work will be scheduled. + * + * The timer routine will simply return once we set FIP_ST_DISABLED. + * This guarantees that no further timeouts or work will be scheduled. + */ +void fcoe_ctlr_destroy(struct fcoe_ctlr *fip) +{ + cancel_work_sync(&fip->recv_work); + skb_queue_purge(&fip->fip_recv_list); + + mutex_lock(&fip->ctlr_mutex); + fcoe_ctlr_set_state(fip, FIP_ST_DISABLED); + fcoe_ctlr_reset_fcfs(fip); + mutex_unlock(&fip->ctlr_mutex); + del_timer_sync(&fip->timer); + cancel_work_sync(&fip->timer_work); +} +EXPORT_SYMBOL(fcoe_ctlr_destroy); + +/** + * fcoe_ctlr_announce() - announce new FCF selection + * @fip: The FCoE controller + * + * Also sets the destination MAC for FCoE and control packets + * + * Called with neither ctlr_mutex nor ctlr_lock held. + */ +static void fcoe_ctlr_announce(struct fcoe_ctlr *fip) +{ + struct fcoe_fcf *sel; + struct fcoe_fcf *fcf; + unsigned long flags; + + mutex_lock(&fip->ctlr_mutex); + spin_lock_irqsave(&fip->ctlr_lock, flags); + + kfree_skb(fip->flogi_req); + fip->flogi_req = NULL; + list_for_each_entry(fcf, &fip->fcfs, list) + fcf->flogi_sent = 0; + + spin_unlock_irqrestore(&fip->ctlr_lock, flags); + sel = fip->sel_fcf; + + if (sel && ether_addr_equal(sel->fcf_mac, fip->dest_addr)) + goto unlock; + if (!is_zero_ether_addr(fip->dest_addr)) { + printk(KERN_NOTICE "libfcoe: host%d: " + "FIP Fibre-Channel Forwarder MAC %pM deselected\n", + fip->lp->host->host_no, fip->dest_addr); + eth_zero_addr(fip->dest_addr); + } + if (sel) { + printk(KERN_INFO "libfcoe: host%d: FIP selected " + "Fibre-Channel Forwarder MAC %pM\n", + fip->lp->host->host_no, sel->fcf_mac); + memcpy(fip->dest_addr, sel->fcoe_mac, ETH_ALEN); + fip->map_dest = 0; + } +unlock: + mutex_unlock(&fip->ctlr_mutex); +} + +/** + * fcoe_ctlr_fcoe_size() - Return the maximum FCoE size required for VN_Port + * @fip: The FCoE controller to get the maximum FCoE size from + * + * Returns the maximum packet size including the FCoE header and trailer, + * but not including any Ethernet or VLAN headers. + */ +static inline u32 fcoe_ctlr_fcoe_size(struct fcoe_ctlr *fip) +{ + /* + * Determine the max FCoE frame size allowed, including + * FCoE header and trailer. + * Note: lp->mfs is currently the payload size, not the frame size. + */ + return fip->lp->mfs + sizeof(struct fc_frame_header) + + sizeof(struct fcoe_hdr) + sizeof(struct fcoe_crc_eof); +} + +/** + * fcoe_ctlr_solicit() - Send a FIP solicitation + * @fip: The FCoE controller to send the solicitation on + * @fcf: The destination FCF (if NULL, a multicast solicitation is sent) + */ +static void fcoe_ctlr_solicit(struct fcoe_ctlr *fip, struct fcoe_fcf *fcf) +{ + struct sk_buff *skb; + struct fip_sol { + struct ethhdr eth; + struct fip_header fip; + struct { + struct fip_mac_desc mac; + struct fip_wwn_desc wwnn; + struct fip_size_desc size; + } __packed desc; + } __packed * sol; + u32 fcoe_size; + + skb = dev_alloc_skb(sizeof(*sol)); + if (!skb) + return; + + sol = (struct fip_sol *)skb->data; + + memset(sol, 0, sizeof(*sol)); + memcpy(sol->eth.h_dest, fcf ? fcf->fcf_mac : fcoe_all_fcfs, ETH_ALEN); + memcpy(sol->eth.h_source, fip->ctl_src_addr, ETH_ALEN); + sol->eth.h_proto = htons(ETH_P_FIP); + + sol->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER); + sol->fip.fip_op = htons(FIP_OP_DISC); + sol->fip.fip_subcode = FIP_SC_SOL; + sol->fip.fip_dl_len = htons(sizeof(sol->desc) / FIP_BPW); + sol->fip.fip_flags = htons(FIP_FL_FPMA); + if (fip->spma) + sol->fip.fip_flags |= htons(FIP_FL_SPMA); + + sol->desc.mac.fd_desc.fip_dtype = FIP_DT_MAC; + sol->desc.mac.fd_desc.fip_dlen = sizeof(sol->desc.mac) / FIP_BPW; + memcpy(sol->desc.mac.fd_mac, fip->ctl_src_addr, ETH_ALEN); + + sol->desc.wwnn.fd_desc.fip_dtype = FIP_DT_NAME; + sol->desc.wwnn.fd_desc.fip_dlen = sizeof(sol->desc.wwnn) / FIP_BPW; + put_unaligned_be64(fip->lp->wwnn, &sol->desc.wwnn.fd_wwn); + + fcoe_size = fcoe_ctlr_fcoe_size(fip); + sol->desc.size.fd_desc.fip_dtype = FIP_DT_FCOE_SIZE; + sol->desc.size.fd_desc.fip_dlen = sizeof(sol->desc.size) / FIP_BPW; + sol->desc.size.fd_size = htons(fcoe_size); + + skb_put(skb, sizeof(*sol)); + skb->protocol = htons(ETH_P_FIP); + skb->priority = fip->priority; + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + fip->send(fip, skb); + + if (!fcf) + fip->sol_time = jiffies; +} + +/** + * fcoe_ctlr_link_up() - Start FCoE controller + * @fip: The FCoE controller to start + * + * Called from the LLD when the network link is ready. + */ +void fcoe_ctlr_link_up(struct fcoe_ctlr *fip) +{ + mutex_lock(&fip->ctlr_mutex); + if (fip->state == FIP_ST_NON_FIP || fip->state == FIP_ST_AUTO) { + mutex_unlock(&fip->ctlr_mutex); + fc_linkup(fip->lp); + } else if (fip->state == FIP_ST_LINK_WAIT) { + if (fip->mode == FIP_MODE_NON_FIP) + fcoe_ctlr_set_state(fip, FIP_ST_NON_FIP); + else + fcoe_ctlr_set_state(fip, FIP_ST_AUTO); + switch (fip->mode) { + default: + LIBFCOE_FIP_DBG(fip, "invalid mode %d\n", fip->mode); + fallthrough; + case FIP_MODE_AUTO: + LIBFCOE_FIP_DBG(fip, "%s", "setting AUTO mode.\n"); + fallthrough; + case FIP_MODE_FABRIC: + case FIP_MODE_NON_FIP: + mutex_unlock(&fip->ctlr_mutex); + fc_linkup(fip->lp); + fcoe_ctlr_solicit(fip, NULL); + break; + case FIP_MODE_VN2VN: + fcoe_ctlr_vn_start(fip); + mutex_unlock(&fip->ctlr_mutex); + fc_linkup(fip->lp); + break; + } + } else + mutex_unlock(&fip->ctlr_mutex); +} +EXPORT_SYMBOL(fcoe_ctlr_link_up); + +/** + * fcoe_ctlr_reset() - Reset a FCoE controller + * @fip: The FCoE controller to reset + */ +static void fcoe_ctlr_reset(struct fcoe_ctlr *fip) +{ + fcoe_ctlr_reset_fcfs(fip); + del_timer(&fip->timer); + fip->ctlr_ka_time = 0; + fip->port_ka_time = 0; + fip->sol_time = 0; + fip->flogi_oxid = FC_XID_UNKNOWN; + fcoe_ctlr_map_dest(fip); +} + +/** + * fcoe_ctlr_link_down() - Stop a FCoE controller + * @fip: The FCoE controller to be stopped + * + * Returns non-zero if the link was up and now isn't. + * + * Called from the LLD when the network link is not ready. + * There may be multiple calls while the link is down. + */ +int fcoe_ctlr_link_down(struct fcoe_ctlr *fip) +{ + int link_dropped; + + LIBFCOE_FIP_DBG(fip, "link down.\n"); + mutex_lock(&fip->ctlr_mutex); + fcoe_ctlr_reset(fip); + link_dropped = fip->state != FIP_ST_LINK_WAIT; + fcoe_ctlr_set_state(fip, FIP_ST_LINK_WAIT); + mutex_unlock(&fip->ctlr_mutex); + + if (link_dropped) + fc_linkdown(fip->lp); + return link_dropped; +} +EXPORT_SYMBOL(fcoe_ctlr_link_down); + +/** + * fcoe_ctlr_send_keep_alive() - Send a keep-alive to the selected FCF + * @fip: The FCoE controller to send the FKA on + * @lport: libfc fc_lport to send from + * @ports: 0 for controller keep-alive, 1 for port keep-alive + * @sa: The source MAC address + * + * A controller keep-alive is sent every fka_period (typically 8 seconds). + * The source MAC is the native MAC address. + * + * A port keep-alive is sent every 90 seconds while logged in. + * The source MAC is the assigned mapped source address. + * The destination is the FCF's F-port. + */ +static void fcoe_ctlr_send_keep_alive(struct fcoe_ctlr *fip, + struct fc_lport *lport, + int ports, u8 *sa) +{ + struct sk_buff *skb; + struct fip_kal { + struct ethhdr eth; + struct fip_header fip; + struct fip_mac_desc mac; + } __packed * kal; + struct fip_vn_desc *vn; + u32 len; + struct fc_lport *lp; + struct fcoe_fcf *fcf; + + fcf = fip->sel_fcf; + lp = fip->lp; + if (!fcf || (ports && !lp->port_id)) + return; + + len = sizeof(*kal) + ports * sizeof(*vn); + skb = dev_alloc_skb(len); + if (!skb) + return; + + kal = (struct fip_kal *)skb->data; + memset(kal, 0, len); + memcpy(kal->eth.h_dest, fcf->fcf_mac, ETH_ALEN); + memcpy(kal->eth.h_source, sa, ETH_ALEN); + kal->eth.h_proto = htons(ETH_P_FIP); + + kal->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER); + kal->fip.fip_op = htons(FIP_OP_CTRL); + kal->fip.fip_subcode = FIP_SC_KEEP_ALIVE; + kal->fip.fip_dl_len = htons((sizeof(kal->mac) + + ports * sizeof(*vn)) / FIP_BPW); + kal->fip.fip_flags = htons(FIP_FL_FPMA); + if (fip->spma) + kal->fip.fip_flags |= htons(FIP_FL_SPMA); + + kal->mac.fd_desc.fip_dtype = FIP_DT_MAC; + kal->mac.fd_desc.fip_dlen = sizeof(kal->mac) / FIP_BPW; + memcpy(kal->mac.fd_mac, fip->ctl_src_addr, ETH_ALEN); + if (ports) { + vn = (struct fip_vn_desc *)(kal + 1); + vn->fd_desc.fip_dtype = FIP_DT_VN_ID; + vn->fd_desc.fip_dlen = sizeof(*vn) / FIP_BPW; + memcpy(vn->fd_mac, fip->get_src_addr(lport), ETH_ALEN); + hton24(vn->fd_fc_id, lport->port_id); + put_unaligned_be64(lport->wwpn, &vn->fd_wwpn); + } + skb_put(skb, len); + skb->protocol = htons(ETH_P_FIP); + skb->priority = fip->priority; + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + fip->send(fip, skb); +} + +/** + * fcoe_ctlr_encaps() - Encapsulate an ELS frame for FIP, without sending it + * @fip: The FCoE controller for the ELS frame + * @lport: The local port + * @dtype: The FIP descriptor type for the frame + * @skb: The FCoE ELS frame including FC header but no FCoE headers + * @d_id: The destination port ID. + * + * Returns non-zero error code on failure. + * + * The caller must check that the length is a multiple of 4. + * + * The @skb must have enough headroom (28 bytes) and tailroom (8 bytes). + * Headroom includes the FIP encapsulation description, FIP header, and + * Ethernet header. The tailroom is for the FIP MAC descriptor. + */ +static int fcoe_ctlr_encaps(struct fcoe_ctlr *fip, struct fc_lport *lport, + u8 dtype, struct sk_buff *skb, u32 d_id) +{ + struct fip_encaps_head { + struct ethhdr eth; + struct fip_header fip; + struct fip_encaps encaps; + } __packed * cap; + struct fc_frame_header *fh; + struct fip_mac_desc *mac; + struct fcoe_fcf *fcf; + size_t dlen; + u16 fip_flags; + u8 op; + + fh = (struct fc_frame_header *)skb->data; + op = *(u8 *)(fh + 1); + dlen = sizeof(struct fip_encaps) + skb->len; /* len before push */ + cap = skb_push(skb, sizeof(*cap)); + memset(cap, 0, sizeof(*cap)); + + if (lport->point_to_multipoint) { + if (fcoe_ctlr_vn_lookup(fip, d_id, cap->eth.h_dest)) + return -ENODEV; + fip_flags = 0; + } else { + fcf = fip->sel_fcf; + if (!fcf) + return -ENODEV; + fip_flags = fcf->flags; + fip_flags &= fip->spma ? FIP_FL_SPMA | FIP_FL_FPMA : + FIP_FL_FPMA; + if (!fip_flags) + return -ENODEV; + memcpy(cap->eth.h_dest, fcf->fcf_mac, ETH_ALEN); + } + memcpy(cap->eth.h_source, fip->ctl_src_addr, ETH_ALEN); + cap->eth.h_proto = htons(ETH_P_FIP); + + cap->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER); + cap->fip.fip_op = htons(FIP_OP_LS); + if (op == ELS_LS_ACC || op == ELS_LS_RJT) + cap->fip.fip_subcode = FIP_SC_REP; + else + cap->fip.fip_subcode = FIP_SC_REQ; + cap->fip.fip_flags = htons(fip_flags); + + cap->encaps.fd_desc.fip_dtype = dtype; + cap->encaps.fd_desc.fip_dlen = dlen / FIP_BPW; + + if (op != ELS_LS_RJT) { + dlen += sizeof(*mac); + mac = skb_put_zero(skb, sizeof(*mac)); + mac->fd_desc.fip_dtype = FIP_DT_MAC; + mac->fd_desc.fip_dlen = sizeof(*mac) / FIP_BPW; + if (dtype != FIP_DT_FLOGI && dtype != FIP_DT_FDISC) { + memcpy(mac->fd_mac, fip->get_src_addr(lport), ETH_ALEN); + } else if (fip->mode == FIP_MODE_VN2VN) { + hton24(mac->fd_mac, FIP_VN_FC_MAP); + hton24(mac->fd_mac + 3, fip->port_id); + } else if (fip_flags & FIP_FL_SPMA) { + LIBFCOE_FIP_DBG(fip, "FLOGI/FDISC sent with SPMA\n"); + memcpy(mac->fd_mac, fip->ctl_src_addr, ETH_ALEN); + } else { + LIBFCOE_FIP_DBG(fip, "FLOGI/FDISC sent with FPMA\n"); + /* FPMA only FLOGI. Must leave the MAC desc zeroed. */ + } + } + cap->fip.fip_dl_len = htons(dlen / FIP_BPW); + + skb->protocol = htons(ETH_P_FIP); + skb->priority = fip->priority; + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + return 0; +} + +/** + * fcoe_ctlr_els_send() - Send an ELS frame encapsulated by FIP if appropriate. + * @fip: FCoE controller. + * @lport: libfc fc_lport to send from + * @skb: FCoE ELS frame including FC header but no FCoE headers. + * + * Returns a non-zero error code if the frame should not be sent. + * Returns zero if the caller should send the frame with FCoE encapsulation. + * + * The caller must check that the length is a multiple of 4. + * The SKB must have enough headroom (28 bytes) and tailroom (8 bytes). + * The the skb must also be an fc_frame. + * + * This is called from the lower-level driver with spinlocks held, + * so we must not take a mutex here. + */ +int fcoe_ctlr_els_send(struct fcoe_ctlr *fip, struct fc_lport *lport, + struct sk_buff *skb) +{ + struct fc_frame *fp; + struct fc_frame_header *fh; + unsigned long flags; + u16 old_xid; + u8 op; + u8 mac[ETH_ALEN]; + + fp = container_of(skb, struct fc_frame, skb); + fh = (struct fc_frame_header *)skb->data; + op = *(u8 *)(fh + 1); + + if (op == ELS_FLOGI && fip->mode != FIP_MODE_VN2VN) { + old_xid = fip->flogi_oxid; + fip->flogi_oxid = ntohs(fh->fh_ox_id); + if (fip->state == FIP_ST_AUTO) { + if (old_xid == FC_XID_UNKNOWN) + fip->flogi_count = 0; + fip->flogi_count++; + if (fip->flogi_count < 3) + goto drop; + fcoe_ctlr_map_dest(fip); + return 0; + } + if (fip->state == FIP_ST_NON_FIP) + fcoe_ctlr_map_dest(fip); + } + + if (fip->state == FIP_ST_NON_FIP) + return 0; + if (!fip->sel_fcf && fip->mode != FIP_MODE_VN2VN) + goto drop; + switch (op) { + case ELS_FLOGI: + op = FIP_DT_FLOGI; + if (fip->mode == FIP_MODE_VN2VN) + break; + spin_lock_irqsave(&fip->ctlr_lock, flags); + kfree_skb(fip->flogi_req); + fip->flogi_req = skb; + fip->flogi_req_send = 1; + spin_unlock_irqrestore(&fip->ctlr_lock, flags); + schedule_work(&fip->timer_work); + return -EINPROGRESS; + case ELS_FDISC: + if (ntoh24(fh->fh_s_id)) + return 0; + op = FIP_DT_FDISC; + break; + case ELS_LOGO: + if (fip->mode == FIP_MODE_VN2VN) { + if (fip->state != FIP_ST_VNMP_UP) + goto drop; + if (ntoh24(fh->fh_d_id) == FC_FID_FLOGI) + goto drop; + } else { + if (fip->state != FIP_ST_ENABLED) + return 0; + if (ntoh24(fh->fh_d_id) != FC_FID_FLOGI) + return 0; + } + op = FIP_DT_LOGO; + break; + case ELS_LS_ACC: + /* + * If non-FIP, we may have gotten an SID by accepting an FLOGI + * from a point-to-point connection. Switch to using + * the source mac based on the SID. The destination + * MAC in this case would have been set by receiving the + * FLOGI. + */ + if (fip->state == FIP_ST_NON_FIP) { + if (fip->flogi_oxid == FC_XID_UNKNOWN) + return 0; + fip->flogi_oxid = FC_XID_UNKNOWN; + fc_fcoe_set_mac(mac, fh->fh_d_id); + fip->update_mac(lport, mac); + } + fallthrough; + case ELS_LS_RJT: + op = fr_encaps(fp); + if (op) + break; + return 0; + default: + if (fip->state != FIP_ST_ENABLED && + fip->state != FIP_ST_VNMP_UP) + goto drop; + return 0; + } + LIBFCOE_FIP_DBG(fip, "els_send op %u d_id %x\n", + op, ntoh24(fh->fh_d_id)); + if (fcoe_ctlr_encaps(fip, lport, op, skb, ntoh24(fh->fh_d_id))) + goto drop; + fip->send(fip, skb); + return -EINPROGRESS; +drop: + LIBFCOE_FIP_DBG(fip, "drop els_send op %u d_id %x\n", + op, ntoh24(fh->fh_d_id)); + kfree_skb(skb); + return -EINVAL; +} +EXPORT_SYMBOL(fcoe_ctlr_els_send); + +/** + * fcoe_ctlr_age_fcfs() - Reset and free all old FCFs for a controller + * @fip: The FCoE controller to free FCFs on + * + * Called with lock held and preemption disabled. + * + * An FCF is considered old if we have missed two advertisements. + * That is, there have been no valid advertisement from it for 2.5 + * times its keep-alive period. + * + * In addition, determine the time when an FCF selection can occur. + * + * Also, increment the MissDiscAdvCount when no advertisement is received + * for the corresponding FCF for 1.5 * FKA_ADV_PERIOD (FC-BB-5 LESB). + * + * Returns the time in jiffies for the next call. + */ +static unsigned long fcoe_ctlr_age_fcfs(struct fcoe_ctlr *fip) +{ + struct fcoe_fcf *fcf; + struct fcoe_fcf *next; + unsigned long next_timer = jiffies + msecs_to_jiffies(FIP_VN_KA_PERIOD); + unsigned long deadline; + unsigned long sel_time = 0; + struct list_head del_list; + struct fc_stats *stats; + + INIT_LIST_HEAD(&del_list); + + stats = per_cpu_ptr(fip->lp->stats, get_cpu()); + + list_for_each_entry_safe(fcf, next, &fip->fcfs, list) { + deadline = fcf->time + fcf->fka_period + fcf->fka_period / 2; + if (fip->sel_fcf == fcf) { + if (time_after(jiffies, deadline)) { + stats->MissDiscAdvCount++; + printk(KERN_INFO "libfcoe: host%d: " + "Missing Discovery Advertisement " + "for fab %16.16llx count %lld\n", + fip->lp->host->host_no, fcf->fabric_name, + stats->MissDiscAdvCount); + } else if (time_after(next_timer, deadline)) + next_timer = deadline; + } + + deadline += fcf->fka_period; + if (time_after_eq(jiffies, deadline)) { + if (fip->sel_fcf == fcf) + fip->sel_fcf = NULL; + /* + * Move to delete list so we can call + * fcoe_sysfs_fcf_del (which can sleep) + * after the put_cpu(). + */ + list_del(&fcf->list); + list_add(&fcf->list, &del_list); + stats->VLinkFailureCount++; + } else { + if (time_after(next_timer, deadline)) + next_timer = deadline; + if (fcoe_ctlr_mtu_valid(fcf) && + (!sel_time || time_before(sel_time, fcf->time))) + sel_time = fcf->time; + } + } + put_cpu(); + + list_for_each_entry_safe(fcf, next, &del_list, list) { + /* Removes fcf from current list */ + fcoe_sysfs_fcf_del(fcf); + } + + if (sel_time && !fip->sel_fcf && !fip->sel_time) { + sel_time += msecs_to_jiffies(FCOE_CTLR_START_DELAY); + fip->sel_time = sel_time; + } + + return next_timer; +} + +/** + * fcoe_ctlr_parse_adv() - Decode a FIP advertisement into a new FCF entry + * @fip: The FCoE controller receiving the advertisement + * @skb: The received FIP advertisement frame + * @fcf: The resulting FCF entry + * + * Returns zero on a valid parsed advertisement, + * otherwise returns non zero value. + */ +static int fcoe_ctlr_parse_adv(struct fcoe_ctlr *fip, + struct sk_buff *skb, struct fcoe_fcf *fcf) +{ + struct fip_header *fiph; + struct fip_desc *desc = NULL; + struct fip_wwn_desc *wwn; + struct fip_fab_desc *fab; + struct fip_fka_desc *fka; + unsigned long t; + size_t rlen; + size_t dlen; + u32 desc_mask; + + memset(fcf, 0, sizeof(*fcf)); + fcf->fka_period = msecs_to_jiffies(FCOE_CTLR_DEF_FKA); + + fiph = (struct fip_header *)skb->data; + fcf->flags = ntohs(fiph->fip_flags); + + /* + * mask of required descriptors. validating each one clears its bit. + */ + desc_mask = BIT(FIP_DT_PRI) | BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME) | + BIT(FIP_DT_FAB) | BIT(FIP_DT_FKA); + + rlen = ntohs(fiph->fip_dl_len) * 4; + if (rlen + sizeof(*fiph) > skb->len) + return -EINVAL; + + desc = (struct fip_desc *)(fiph + 1); + while (rlen > 0) { + dlen = desc->fip_dlen * FIP_BPW; + if (dlen < sizeof(*desc) || dlen > rlen) + return -EINVAL; + /* Drop Adv if there are duplicate critical descriptors */ + if ((desc->fip_dtype < 32) && + !(desc_mask & 1U << desc->fip_dtype)) { + LIBFCOE_FIP_DBG(fip, "Duplicate Critical " + "Descriptors in FIP adv\n"); + return -EINVAL; + } + switch (desc->fip_dtype) { + case FIP_DT_PRI: + if (dlen != sizeof(struct fip_pri_desc)) + goto len_err; + fcf->pri = ((struct fip_pri_desc *)desc)->fd_pri; + desc_mask &= ~BIT(FIP_DT_PRI); + break; + case FIP_DT_MAC: + if (dlen != sizeof(struct fip_mac_desc)) + goto len_err; + memcpy(fcf->fcf_mac, + ((struct fip_mac_desc *)desc)->fd_mac, + ETH_ALEN); + memcpy(fcf->fcoe_mac, fcf->fcf_mac, ETH_ALEN); + if (!is_valid_ether_addr(fcf->fcf_mac)) { + LIBFCOE_FIP_DBG(fip, + "Invalid MAC addr %pM in FIP adv\n", + fcf->fcf_mac); + return -EINVAL; + } + desc_mask &= ~BIT(FIP_DT_MAC); + break; + case FIP_DT_NAME: + if (dlen != sizeof(struct fip_wwn_desc)) + goto len_err; + wwn = (struct fip_wwn_desc *)desc; + fcf->switch_name = get_unaligned_be64(&wwn->fd_wwn); + desc_mask &= ~BIT(FIP_DT_NAME); + break; + case FIP_DT_FAB: + if (dlen != sizeof(struct fip_fab_desc)) + goto len_err; + fab = (struct fip_fab_desc *)desc; + fcf->fabric_name = get_unaligned_be64(&fab->fd_wwn); + fcf->vfid = ntohs(fab->fd_vfid); + fcf->fc_map = ntoh24(fab->fd_map); + desc_mask &= ~BIT(FIP_DT_FAB); + break; + case FIP_DT_FKA: + if (dlen != sizeof(struct fip_fka_desc)) + goto len_err; + fka = (struct fip_fka_desc *)desc; + if (fka->fd_flags & FIP_FKA_ADV_D) + fcf->fd_flags = 1; + t = ntohl(fka->fd_fka_period); + if (t >= FCOE_CTLR_MIN_FKA) + fcf->fka_period = msecs_to_jiffies(t); + desc_mask &= ~BIT(FIP_DT_FKA); + break; + case FIP_DT_MAP_OUI: + case FIP_DT_FCOE_SIZE: + case FIP_DT_FLOGI: + case FIP_DT_FDISC: + case FIP_DT_LOGO: + case FIP_DT_ELP: + default: + LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x " + "in FIP adv\n", desc->fip_dtype); + /* standard says ignore unknown descriptors >= 128 */ + if (desc->fip_dtype < FIP_DT_NON_CRITICAL) + return -EINVAL; + break; + } + desc = (struct fip_desc *)((char *)desc + dlen); + rlen -= dlen; + } + if (!fcf->fc_map || (fcf->fc_map & 0x10000)) + return -EINVAL; + if (!fcf->switch_name) + return -EINVAL; + if (desc_mask) { + LIBFCOE_FIP_DBG(fip, "adv missing descriptors mask %x\n", + desc_mask); + return -EINVAL; + } + return 0; + +len_err: + LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n", + desc->fip_dtype, dlen); + return -EINVAL; +} + +/** + * fcoe_ctlr_recv_adv() - Handle an incoming advertisement + * @fip: The FCoE controller receiving the advertisement + * @skb: The received FIP packet + */ +static void fcoe_ctlr_recv_adv(struct fcoe_ctlr *fip, struct sk_buff *skb) +{ + struct fcoe_fcf *fcf; + struct fcoe_fcf new; + unsigned long sol_tov = msecs_to_jiffies(FCOE_CTLR_SOL_TOV); + int first = 0; + int mtu_valid; + int found = 0; + int rc = 0; + + if (fcoe_ctlr_parse_adv(fip, skb, &new)) + return; + + mutex_lock(&fip->ctlr_mutex); + first = list_empty(&fip->fcfs); + list_for_each_entry(fcf, &fip->fcfs, list) { + if (fcf->switch_name == new.switch_name && + fcf->fabric_name == new.fabric_name && + fcf->fc_map == new.fc_map && + ether_addr_equal(fcf->fcf_mac, new.fcf_mac)) { + found = 1; + break; + } + } + if (!found) { + if (fip->fcf_count >= FCOE_CTLR_FCF_LIMIT) + goto out; + + fcf = kmalloc(sizeof(*fcf), GFP_ATOMIC); + if (!fcf) + goto out; + + memcpy(fcf, &new, sizeof(new)); + fcf->fip = fip; + rc = fcoe_sysfs_fcf_add(fcf); + if (rc) { + printk(KERN_ERR "Failed to allocate sysfs instance " + "for FCF, fab %16.16llx mac %pM\n", + new.fabric_name, new.fcf_mac); + kfree(fcf); + goto out; + } + } else { + /* + * Update the FCF's keep-alive descriptor flags. + * Other flag changes from new advertisements are + * ignored after a solicited advertisement is + * received and the FCF is selectable (usable). + */ + fcf->fd_flags = new.fd_flags; + if (!fcoe_ctlr_fcf_usable(fcf)) + fcf->flags = new.flags; + + if (fcf == fip->sel_fcf && !fcf->fd_flags) { + fip->ctlr_ka_time -= fcf->fka_period; + fip->ctlr_ka_time += new.fka_period; + if (time_before(fip->ctlr_ka_time, fip->timer.expires)) + mod_timer(&fip->timer, fip->ctlr_ka_time); + } + fcf->fka_period = new.fka_period; + memcpy(fcf->fcf_mac, new.fcf_mac, ETH_ALEN); + } + + mtu_valid = fcoe_ctlr_mtu_valid(fcf); + fcf->time = jiffies; + if (!found) + LIBFCOE_FIP_DBG(fip, "New FCF fab %16.16llx mac %pM\n", + fcf->fabric_name, fcf->fcf_mac); + + /* + * If this advertisement is not solicited and our max receive size + * hasn't been verified, send a solicited advertisement. + */ + if (!mtu_valid) + fcoe_ctlr_solicit(fip, fcf); + + /* + * If its been a while since we did a solicit, and this is + * the first advertisement we've received, do a multicast + * solicitation to gather as many advertisements as we can + * before selection occurs. + */ + if (first && time_after(jiffies, fip->sol_time + sol_tov)) + fcoe_ctlr_solicit(fip, NULL); + + /* + * Put this FCF at the head of the list for priority among equals. + * This helps in the case of an NPV switch which insists we use + * the FCF that answers multicast solicitations, not the others that + * are sending periodic multicast advertisements. + */ + if (mtu_valid) + list_move(&fcf->list, &fip->fcfs); + + /* + * If this is the first validated FCF, note the time and + * set a timer to trigger selection. + */ + if (mtu_valid && !fip->sel_fcf && !fip->sel_time && + fcoe_ctlr_fcf_usable(fcf)) { + fip->sel_time = jiffies + + msecs_to_jiffies(FCOE_CTLR_START_DELAY); + if (!timer_pending(&fip->timer) || + time_before(fip->sel_time, fip->timer.expires)) + mod_timer(&fip->timer, fip->sel_time); + } + +out: + mutex_unlock(&fip->ctlr_mutex); +} + +/** + * fcoe_ctlr_recv_els() - Handle an incoming FIP encapsulated ELS frame + * @fip: The FCoE controller which received the packet + * @skb: The received FIP packet + */ +static void fcoe_ctlr_recv_els(struct fcoe_ctlr *fip, struct sk_buff *skb) +{ + struct fc_lport *lport = fip->lp; + struct fip_header *fiph; + struct fc_frame *fp = (struct fc_frame *)skb; + struct fc_frame_header *fh = NULL; + struct fip_desc *desc; + struct fip_encaps *els; + struct fcoe_fcf *sel; + struct fc_stats *stats; + enum fip_desc_type els_dtype = 0; + u8 els_op; + u8 sub; + u8 granted_mac[ETH_ALEN] = { 0 }; + size_t els_len = 0; + size_t rlen; + size_t dlen; + u32 desc_mask = 0; + u32 desc_cnt = 0; + + fiph = (struct fip_header *)skb->data; + sub = fiph->fip_subcode; + if (sub != FIP_SC_REQ && sub != FIP_SC_REP) + goto drop; + + rlen = ntohs(fiph->fip_dl_len) * 4; + if (rlen + sizeof(*fiph) > skb->len) + goto drop; + + desc = (struct fip_desc *)(fiph + 1); + while (rlen > 0) { + desc_cnt++; + dlen = desc->fip_dlen * FIP_BPW; + if (dlen < sizeof(*desc) || dlen > rlen) + goto drop; + /* Drop ELS if there are duplicate critical descriptors */ + if (desc->fip_dtype < 32) { + if ((desc->fip_dtype != FIP_DT_MAC) && + (desc_mask & 1U << desc->fip_dtype)) { + LIBFCOE_FIP_DBG(fip, "Duplicate Critical " + "Descriptors in FIP ELS\n"); + goto drop; + } + desc_mask |= (1 << desc->fip_dtype); + } + switch (desc->fip_dtype) { + case FIP_DT_MAC: + sel = fip->sel_fcf; + if (desc_cnt == 1) { + LIBFCOE_FIP_DBG(fip, "FIP descriptors " + "received out of order\n"); + goto drop; + } + /* + * Some switch implementations send two MAC descriptors, + * with first MAC(granted_mac) being the FPMA, and the + * second one(fcoe_mac) is used as destination address + * for sending/receiving FCoE packets. FIP traffic is + * sent using fip_mac. For regular switches, both + * fip_mac and fcoe_mac would be the same. + */ + if (desc_cnt == 2) + memcpy(granted_mac, + ((struct fip_mac_desc *)desc)->fd_mac, + ETH_ALEN); + + if (dlen != sizeof(struct fip_mac_desc)) + goto len_err; + + if ((desc_cnt == 3) && (sel)) + memcpy(sel->fcoe_mac, + ((struct fip_mac_desc *)desc)->fd_mac, + ETH_ALEN); + break; + case FIP_DT_FLOGI: + case FIP_DT_FDISC: + case FIP_DT_LOGO: + case FIP_DT_ELP: + if (desc_cnt != 1) { + LIBFCOE_FIP_DBG(fip, "FIP descriptors " + "received out of order\n"); + goto drop; + } + if (fh) + goto drop; + if (dlen < sizeof(*els) + sizeof(*fh) + 1) + goto len_err; + els_len = dlen - sizeof(*els); + els = (struct fip_encaps *)desc; + fh = (struct fc_frame_header *)(els + 1); + els_dtype = desc->fip_dtype; + break; + default: + LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x " + "in FIP adv\n", desc->fip_dtype); + /* standard says ignore unknown descriptors >= 128 */ + if (desc->fip_dtype < FIP_DT_NON_CRITICAL) + goto drop; + if (desc_cnt <= 2) { + LIBFCOE_FIP_DBG(fip, "FIP descriptors " + "received out of order\n"); + goto drop; + } + break; + } + desc = (struct fip_desc *)((char *)desc + dlen); + rlen -= dlen; + } + + if (!fh) + goto drop; + els_op = *(u8 *)(fh + 1); + + if ((els_dtype == FIP_DT_FLOGI || els_dtype == FIP_DT_FDISC) && + sub == FIP_SC_REP && fip->mode != FIP_MODE_VN2VN) { + if (els_op == ELS_LS_ACC) { + if (!is_valid_ether_addr(granted_mac)) { + LIBFCOE_FIP_DBG(fip, + "Invalid MAC address %pM in FIP ELS\n", + granted_mac); + goto drop; + } + memcpy(fr_cb(fp)->granted_mac, granted_mac, ETH_ALEN); + + if (fip->flogi_oxid == ntohs(fh->fh_ox_id)) { + fip->flogi_oxid = FC_XID_UNKNOWN; + if (els_dtype == FIP_DT_FLOGI) + fcoe_ctlr_announce(fip); + } + } else if (els_dtype == FIP_DT_FLOGI && + !fcoe_ctlr_flogi_retry(fip)) + goto drop; /* retrying FLOGI so drop reject */ + } + + if ((desc_cnt == 0) || ((els_op != ELS_LS_RJT) && + (!(1U << FIP_DT_MAC & desc_mask)))) { + LIBFCOE_FIP_DBG(fip, "Missing critical descriptors " + "in FIP ELS\n"); + goto drop; + } + + /* + * Convert skb into an fc_frame containing only the ELS. + */ + skb_pull(skb, (u8 *)fh - skb->data); + skb_trim(skb, els_len); + fp = (struct fc_frame *)skb; + fc_frame_init(fp); + fr_sof(fp) = FC_SOF_I3; + fr_eof(fp) = FC_EOF_T; + fr_dev(fp) = lport; + fr_encaps(fp) = els_dtype; + + stats = per_cpu_ptr(lport->stats, get_cpu()); + stats->RxFrames++; + stats->RxWords += skb->len / FIP_BPW; + put_cpu(); + + fc_exch_recv(lport, fp); + return; + +len_err: + LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n", + desc->fip_dtype, dlen); +drop: + kfree_skb(skb); +} + +/** + * fcoe_ctlr_recv_els() - Handle an incoming link reset frame + * @fip: The FCoE controller that received the frame + * @skb: The received FIP packet + * + * There may be multiple VN_Port descriptors. + * The overall length has already been checked. + */ +static void fcoe_ctlr_recv_clr_vlink(struct fcoe_ctlr *fip, + struct sk_buff *skb) +{ + struct fip_desc *desc; + struct fip_mac_desc *mp; + struct fip_wwn_desc *wp; + struct fip_vn_desc *vp; + size_t rlen; + size_t dlen; + struct fcoe_fcf *fcf = fip->sel_fcf; + struct fc_lport *lport = fip->lp; + struct fc_lport *vn_port = NULL; + u32 desc_mask; + int num_vlink_desc; + int reset_phys_port = 0; + struct fip_vn_desc **vlink_desc_arr = NULL; + struct fip_header *fh = (struct fip_header *)skb->data; + struct ethhdr *eh = eth_hdr(skb); + + LIBFCOE_FIP_DBG(fip, "Clear Virtual Link received\n"); + + if (!fcf) { + /* + * We are yet to select best FCF, but we got CVL in the + * meantime. reset the ctlr and let it rediscover the FCF + */ + LIBFCOE_FIP_DBG(fip, "Resetting fcoe_ctlr as FCF has not been " + "selected yet\n"); + mutex_lock(&fip->ctlr_mutex); + fcoe_ctlr_reset(fip); + mutex_unlock(&fip->ctlr_mutex); + return; + } + + /* + * If we've selected an FCF check that the CVL is from there to avoid + * processing CVLs from an unexpected source. If it is from an + * unexpected source drop it on the floor. + */ + if (!ether_addr_equal(eh->h_source, fcf->fcf_mac)) { + LIBFCOE_FIP_DBG(fip, "Dropping CVL due to source address " + "mismatch with FCF src=%pM\n", eh->h_source); + return; + } + + /* + * If we haven't logged into the fabric but receive a CVL we should + * reset everything and go back to solicitation. + */ + if (!lport->port_id) { + LIBFCOE_FIP_DBG(fip, "lport not logged in, resoliciting\n"); + mutex_lock(&fip->ctlr_mutex); + fcoe_ctlr_reset(fip); + mutex_unlock(&fip->ctlr_mutex); + fc_lport_reset(fip->lp); + fcoe_ctlr_solicit(fip, NULL); + return; + } + + /* + * mask of required descriptors. Validating each one clears its bit. + */ + desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME); + + rlen = ntohs(fh->fip_dl_len) * FIP_BPW; + desc = (struct fip_desc *)(fh + 1); + + /* + * Actually need to subtract 'sizeof(*mp) - sizeof(*wp)' from 'rlen' + * before determining max Vx_Port descriptor but a buggy FCF could have + * omitted either or both MAC Address and Name Identifier descriptors + */ + num_vlink_desc = rlen / sizeof(*vp); + if (num_vlink_desc) + vlink_desc_arr = kmalloc_array(num_vlink_desc, sizeof(vp), + GFP_ATOMIC); + if (!vlink_desc_arr) + return; + num_vlink_desc = 0; + + while (rlen >= sizeof(*desc)) { + dlen = desc->fip_dlen * FIP_BPW; + if (dlen > rlen) + goto err; + /* Drop CVL if there are duplicate critical descriptors */ + if ((desc->fip_dtype < 32) && + (desc->fip_dtype != FIP_DT_VN_ID) && + !(desc_mask & 1U << desc->fip_dtype)) { + LIBFCOE_FIP_DBG(fip, "Duplicate Critical " + "Descriptors in FIP CVL\n"); + goto err; + } + switch (desc->fip_dtype) { + case FIP_DT_MAC: + mp = (struct fip_mac_desc *)desc; + if (dlen < sizeof(*mp)) + goto err; + if (!ether_addr_equal(mp->fd_mac, fcf->fcf_mac)) + goto err; + desc_mask &= ~BIT(FIP_DT_MAC); + break; + case FIP_DT_NAME: + wp = (struct fip_wwn_desc *)desc; + if (dlen < sizeof(*wp)) + goto err; + if (get_unaligned_be64(&wp->fd_wwn) != fcf->switch_name) + goto err; + desc_mask &= ~BIT(FIP_DT_NAME); + break; + case FIP_DT_VN_ID: + vp = (struct fip_vn_desc *)desc; + if (dlen < sizeof(*vp)) + goto err; + vlink_desc_arr[num_vlink_desc++] = vp; + vn_port = fc_vport_id_lookup(lport, + ntoh24(vp->fd_fc_id)); + if (vn_port && (vn_port == lport)) { + mutex_lock(&fip->ctlr_mutex); + per_cpu_ptr(lport->stats, + get_cpu())->VLinkFailureCount++; + put_cpu(); + fcoe_ctlr_reset(fip); + mutex_unlock(&fip->ctlr_mutex); + } + break; + default: + /* standard says ignore unknown descriptors >= 128 */ + if (desc->fip_dtype < FIP_DT_NON_CRITICAL) + goto err; + break; + } + desc = (struct fip_desc *)((char *)desc + dlen); + rlen -= dlen; + } + + /* + * reset only if all required descriptors were present and valid. + */ + if (desc_mask) + LIBFCOE_FIP_DBG(fip, "missing descriptors mask %x\n", + desc_mask); + else if (!num_vlink_desc) { + LIBFCOE_FIP_DBG(fip, "CVL: no Vx_Port descriptor found\n"); + /* + * No Vx_Port description. Clear all NPIV ports, + * followed by physical port + */ + mutex_lock(&fip->ctlr_mutex); + per_cpu_ptr(lport->stats, get_cpu())->VLinkFailureCount++; + put_cpu(); + fcoe_ctlr_reset(fip); + mutex_unlock(&fip->ctlr_mutex); + + mutex_lock(&lport->lp_mutex); + list_for_each_entry(vn_port, &lport->vports, list) + fc_lport_reset(vn_port); + mutex_unlock(&lport->lp_mutex); + + fc_lport_reset(fip->lp); + fcoe_ctlr_solicit(fip, NULL); + } else { + int i; + + LIBFCOE_FIP_DBG(fip, "performing Clear Virtual Link\n"); + for (i = 0; i < num_vlink_desc; i++) { + vp = vlink_desc_arr[i]; + vn_port = fc_vport_id_lookup(lport, + ntoh24(vp->fd_fc_id)); + if (!vn_port) + continue; + + /* + * 'port_id' is already validated, check MAC address and + * wwpn + */ + if (!ether_addr_equal(fip->get_src_addr(vn_port), + vp->fd_mac) || + get_unaligned_be64(&vp->fd_wwpn) != + vn_port->wwpn) + continue; + + if (vn_port == lport) + /* + * Physical port, defer processing till all + * listed NPIV ports are cleared + */ + reset_phys_port = 1; + else /* NPIV port */ + fc_lport_reset(vn_port); + } + + if (reset_phys_port) { + fc_lport_reset(fip->lp); + fcoe_ctlr_solicit(fip, NULL); + } + } + +err: + kfree(vlink_desc_arr); +} + +/** + * fcoe_ctlr_recv() - Receive a FIP packet + * @fip: The FCoE controller that received the packet + * @skb: The received FIP packet + * + * This may be called from either NET_RX_SOFTIRQ or IRQ. + */ +void fcoe_ctlr_recv(struct fcoe_ctlr *fip, struct sk_buff *skb) +{ + skb = skb_share_check(skb, GFP_ATOMIC); + if (!skb) + return; + skb_queue_tail(&fip->fip_recv_list, skb); + schedule_work(&fip->recv_work); +} +EXPORT_SYMBOL(fcoe_ctlr_recv); + +/** + * fcoe_ctlr_recv_handler() - Receive a FIP frame + * @fip: The FCoE controller that received the frame + * @skb: The received FIP frame + * + * Returns non-zero if the frame is dropped. + */ +static int fcoe_ctlr_recv_handler(struct fcoe_ctlr *fip, struct sk_buff *skb) +{ + struct fip_header *fiph; + struct ethhdr *eh; + enum fip_state state; + bool fip_vlan_resp = false; + u16 op; + u8 sub; + + if (skb_linearize(skb)) + goto drop; + if (skb->len < sizeof(*fiph)) + goto drop; + eh = eth_hdr(skb); + if (fip->mode == FIP_MODE_VN2VN) { + if (!ether_addr_equal(eh->h_dest, fip->ctl_src_addr) && + !ether_addr_equal(eh->h_dest, fcoe_all_vn2vn) && + !ether_addr_equal(eh->h_dest, fcoe_all_p2p)) + goto drop; + } else if (!ether_addr_equal(eh->h_dest, fip->ctl_src_addr) && + !ether_addr_equal(eh->h_dest, fcoe_all_enode)) + goto drop; + fiph = (struct fip_header *)skb->data; + op = ntohs(fiph->fip_op); + sub = fiph->fip_subcode; + + if (FIP_VER_DECAPS(fiph->fip_ver) != FIP_VER) + goto drop; + if (ntohs(fiph->fip_dl_len) * FIP_BPW + sizeof(*fiph) > skb->len) + goto drop; + + mutex_lock(&fip->ctlr_mutex); + state = fip->state; + if (state == FIP_ST_AUTO) { + fip->map_dest = 0; + fcoe_ctlr_set_state(fip, FIP_ST_ENABLED); + state = FIP_ST_ENABLED; + LIBFCOE_FIP_DBG(fip, "Using FIP mode\n"); + } + fip_vlan_resp = fip->fip_resp; + mutex_unlock(&fip->ctlr_mutex); + + if (fip->mode == FIP_MODE_VN2VN && op == FIP_OP_VN2VN) + return fcoe_ctlr_vn_recv(fip, skb); + + if (fip_vlan_resp && op == FIP_OP_VLAN) { + LIBFCOE_FIP_DBG(fip, "fip vlan discovery\n"); + return fcoe_ctlr_vlan_recv(fip, skb); + } + + if (state != FIP_ST_ENABLED && state != FIP_ST_VNMP_UP && + state != FIP_ST_VNMP_CLAIM) + goto drop; + + if (op == FIP_OP_LS) { + fcoe_ctlr_recv_els(fip, skb); /* consumes skb */ + return 0; + } + + if (state != FIP_ST_ENABLED) + goto drop; + + if (op == FIP_OP_DISC && sub == FIP_SC_ADV) + fcoe_ctlr_recv_adv(fip, skb); + else if (op == FIP_OP_CTRL && sub == FIP_SC_CLR_VLINK) + fcoe_ctlr_recv_clr_vlink(fip, skb); + kfree_skb(skb); + return 0; +drop: + kfree_skb(skb); + return -1; +} + +/** + * fcoe_ctlr_select() - Select the best FCF (if possible) + * @fip: The FCoE controller + * + * Returns the selected FCF, or NULL if none are usable. + * + * If there are conflicting advertisements, no FCF can be chosen. + * + * If there is already a selected FCF, this will choose a better one or + * an equivalent one that hasn't already been sent a FLOGI. + * + * Called with lock held. + */ +static struct fcoe_fcf *fcoe_ctlr_select(struct fcoe_ctlr *fip) +{ + struct fcoe_fcf *fcf; + struct fcoe_fcf *best = fip->sel_fcf; + + list_for_each_entry(fcf, &fip->fcfs, list) { + LIBFCOE_FIP_DBG(fip, "consider FCF fab %16.16llx " + "VFID %d mac %pM map %x val %d " + "sent %u pri %u\n", + fcf->fabric_name, fcf->vfid, fcf->fcf_mac, + fcf->fc_map, fcoe_ctlr_mtu_valid(fcf), + fcf->flogi_sent, fcf->pri); + if (!fcoe_ctlr_fcf_usable(fcf)) { + LIBFCOE_FIP_DBG(fip, "FCF for fab %16.16llx " + "map %x %svalid %savailable\n", + fcf->fabric_name, fcf->fc_map, + (fcf->flags & FIP_FL_SOL) ? "" : "in", + (fcf->flags & FIP_FL_AVAIL) ? + "" : "un"); + continue; + } + if (!best || fcf->pri < best->pri || best->flogi_sent) + best = fcf; + if (fcf->fabric_name != best->fabric_name || + fcf->vfid != best->vfid || + fcf->fc_map != best->fc_map) { + LIBFCOE_FIP_DBG(fip, "Conflicting fabric, VFID, " + "or FC-MAP\n"); + return NULL; + } + } + fip->sel_fcf = best; + if (best) { + LIBFCOE_FIP_DBG(fip, "using FCF mac %pM\n", best->fcf_mac); + fip->port_ka_time = jiffies + + msecs_to_jiffies(FIP_VN_KA_PERIOD); + fip->ctlr_ka_time = jiffies + best->fka_period; + if (time_before(fip->ctlr_ka_time, fip->timer.expires)) + mod_timer(&fip->timer, fip->ctlr_ka_time); + } + return best; +} + +/** + * fcoe_ctlr_flogi_send_locked() - send FIP-encapsulated FLOGI to current FCF + * @fip: The FCoE controller + * + * Returns non-zero error if it could not be sent. + * + * Called with ctlr_mutex and ctlr_lock held. + * Caller must verify that fip->sel_fcf is not NULL. + */ +static int fcoe_ctlr_flogi_send_locked(struct fcoe_ctlr *fip) +{ + struct sk_buff *skb; + struct sk_buff *skb_orig; + struct fc_frame_header *fh; + int error; + + skb_orig = fip->flogi_req; + if (!skb_orig) + return -EINVAL; + + /* + * Clone and send the FLOGI request. If clone fails, use original. + */ + skb = skb_clone(skb_orig, GFP_ATOMIC); + if (!skb) { + skb = skb_orig; + fip->flogi_req = NULL; + } + fh = (struct fc_frame_header *)skb->data; + error = fcoe_ctlr_encaps(fip, fip->lp, FIP_DT_FLOGI, skb, + ntoh24(fh->fh_d_id)); + if (error) { + kfree_skb(skb); + return error; + } + fip->send(fip, skb); + fip->sel_fcf->flogi_sent = 1; + return 0; +} + +/** + * fcoe_ctlr_flogi_retry() - resend FLOGI request to a new FCF if possible + * @fip: The FCoE controller + * + * Returns non-zero error code if there's no FLOGI request to retry or + * no alternate FCF available. + */ +static int fcoe_ctlr_flogi_retry(struct fcoe_ctlr *fip) +{ + struct fcoe_fcf *fcf; + unsigned long flags; + int error; + + mutex_lock(&fip->ctlr_mutex); + spin_lock_irqsave(&fip->ctlr_lock, flags); + LIBFCOE_FIP_DBG(fip, "re-sending FLOGI - reselect\n"); + fcf = fcoe_ctlr_select(fip); + if (!fcf || fcf->flogi_sent) { + kfree_skb(fip->flogi_req); + fip->flogi_req = NULL; + error = -ENOENT; + } else { + fcoe_ctlr_solicit(fip, NULL); + error = fcoe_ctlr_flogi_send_locked(fip); + } + spin_unlock_irqrestore(&fip->ctlr_lock, flags); + mutex_unlock(&fip->ctlr_mutex); + return error; +} + + +/** + * fcoe_ctlr_flogi_send() - Handle sending of FIP FLOGI. + * @fip: The FCoE controller that timed out + * + * Done here because fcoe_ctlr_els_send() can't get mutex. + * + * Called with ctlr_mutex held. The caller must not hold ctlr_lock. + */ +static void fcoe_ctlr_flogi_send(struct fcoe_ctlr *fip) +{ + struct fcoe_fcf *fcf; + unsigned long flags; + + spin_lock_irqsave(&fip->ctlr_lock, flags); + fcf = fip->sel_fcf; + if (!fcf || !fip->flogi_req_send) + goto unlock; + + LIBFCOE_FIP_DBG(fip, "sending FLOGI\n"); + + /* + * If this FLOGI is being sent due to a timeout retry + * to the same FCF as before, select a different FCF if possible. + */ + if (fcf->flogi_sent) { + LIBFCOE_FIP_DBG(fip, "sending FLOGI - reselect\n"); + fcf = fcoe_ctlr_select(fip); + if (!fcf || fcf->flogi_sent) { + LIBFCOE_FIP_DBG(fip, "sending FLOGI - clearing\n"); + list_for_each_entry(fcf, &fip->fcfs, list) + fcf->flogi_sent = 0; + fcf = fcoe_ctlr_select(fip); + } + } + if (fcf) { + fcoe_ctlr_flogi_send_locked(fip); + fip->flogi_req_send = 0; + } else /* XXX */ + LIBFCOE_FIP_DBG(fip, "No FCF selected - defer send\n"); +unlock: + spin_unlock_irqrestore(&fip->ctlr_lock, flags); +} + +/** + * fcoe_ctlr_timeout() - FIP timeout handler + * @t: Timer context use to obtain the controller reference + */ +static void fcoe_ctlr_timeout(struct timer_list *t) +{ + struct fcoe_ctlr *fip = from_timer(fip, t, timer); + + schedule_work(&fip->timer_work); +} + +/** + * fcoe_ctlr_timer_work() - Worker thread function for timer work + * @work: Handle to a FCoE controller + * + * Ages FCFs. Triggers FCF selection if possible. + * Sends keep-alives and resets. + */ +static void fcoe_ctlr_timer_work(struct work_struct *work) +{ + struct fcoe_ctlr *fip; + struct fc_lport *vport; + u8 *mac; + u8 reset = 0; + u8 send_ctlr_ka = 0; + u8 send_port_ka = 0; + struct fcoe_fcf *sel; + struct fcoe_fcf *fcf; + unsigned long next_timer; + + fip = container_of(work, struct fcoe_ctlr, timer_work); + if (fip->mode == FIP_MODE_VN2VN) + return fcoe_ctlr_vn_timeout(fip); + mutex_lock(&fip->ctlr_mutex); + if (fip->state == FIP_ST_DISABLED) { + mutex_unlock(&fip->ctlr_mutex); + return; + } + + fcf = fip->sel_fcf; + next_timer = fcoe_ctlr_age_fcfs(fip); + + sel = fip->sel_fcf; + if (!sel && fip->sel_time) { + if (time_after_eq(jiffies, fip->sel_time)) { + sel = fcoe_ctlr_select(fip); + fip->sel_time = 0; + } else if (time_after(next_timer, fip->sel_time)) + next_timer = fip->sel_time; + } + + if (sel && fip->flogi_req_send) + fcoe_ctlr_flogi_send(fip); + else if (!sel && fcf) + reset = 1; + + if (sel && !sel->fd_flags) { + if (time_after_eq(jiffies, fip->ctlr_ka_time)) { + fip->ctlr_ka_time = jiffies + sel->fka_period; + send_ctlr_ka = 1; + } + if (time_after(next_timer, fip->ctlr_ka_time)) + next_timer = fip->ctlr_ka_time; + + if (time_after_eq(jiffies, fip->port_ka_time)) { + fip->port_ka_time = jiffies + + msecs_to_jiffies(FIP_VN_KA_PERIOD); + send_port_ka = 1; + } + if (time_after(next_timer, fip->port_ka_time)) + next_timer = fip->port_ka_time; + } + if (!list_empty(&fip->fcfs)) + mod_timer(&fip->timer, next_timer); + mutex_unlock(&fip->ctlr_mutex); + + if (reset) { + fc_lport_reset(fip->lp); + /* restart things with a solicitation */ + fcoe_ctlr_solicit(fip, NULL); + } + + if (send_ctlr_ka) + fcoe_ctlr_send_keep_alive(fip, NULL, 0, fip->ctl_src_addr); + + if (send_port_ka) { + mutex_lock(&fip->lp->lp_mutex); + mac = fip->get_src_addr(fip->lp); + fcoe_ctlr_send_keep_alive(fip, fip->lp, 1, mac); + list_for_each_entry(vport, &fip->lp->vports, list) { + mac = fip->get_src_addr(vport); + fcoe_ctlr_send_keep_alive(fip, vport, 1, mac); + } + mutex_unlock(&fip->lp->lp_mutex); + } +} + +/** + * fcoe_ctlr_recv_work() - Worker thread function for receiving FIP frames + * @recv_work: Handle to a FCoE controller + */ +static void fcoe_ctlr_recv_work(struct work_struct *recv_work) +{ + struct fcoe_ctlr *fip; + struct sk_buff *skb; + + fip = container_of(recv_work, struct fcoe_ctlr, recv_work); + while ((skb = skb_dequeue(&fip->fip_recv_list))) + fcoe_ctlr_recv_handler(fip, skb); +} + +/** + * fcoe_ctlr_recv_flogi() - Snoop pre-FIP receipt of FLOGI response + * @fip: The FCoE controller + * @lport: The local port + * @fp: The FC frame to snoop + * + * Snoop potential response to FLOGI or even incoming FLOGI. + * + * The caller has checked that we are waiting for login as indicated + * by fip->flogi_oxid != FC_XID_UNKNOWN. + * + * The caller is responsible for freeing the frame. + * Fill in the granted_mac address. + * + * Return non-zero if the frame should not be delivered to libfc. + */ +int fcoe_ctlr_recv_flogi(struct fcoe_ctlr *fip, struct fc_lport *lport, + struct fc_frame *fp) +{ + struct fc_frame_header *fh; + u8 op; + u8 *sa; + + sa = eth_hdr(&fp->skb)->h_source; + fh = fc_frame_header_get(fp); + if (fh->fh_type != FC_TYPE_ELS) + return 0; + + op = fc_frame_payload_op(fp); + if (op == ELS_LS_ACC && fh->fh_r_ctl == FC_RCTL_ELS_REP && + fip->flogi_oxid == ntohs(fh->fh_ox_id)) { + + mutex_lock(&fip->ctlr_mutex); + if (fip->state != FIP_ST_AUTO && fip->state != FIP_ST_NON_FIP) { + mutex_unlock(&fip->ctlr_mutex); + return -EINVAL; + } + fcoe_ctlr_set_state(fip, FIP_ST_NON_FIP); + LIBFCOE_FIP_DBG(fip, + "received FLOGI LS_ACC using non-FIP mode\n"); + + /* + * FLOGI accepted. + * If the src mac addr is FC_OUI-based, then we mark the + * address_mode flag to use FC_OUI-based Ethernet DA. + * Otherwise we use the FCoE gateway addr + */ + if (ether_addr_equal(sa, (u8[6])FC_FCOE_FLOGI_MAC)) { + fcoe_ctlr_map_dest(fip); + } else { + memcpy(fip->dest_addr, sa, ETH_ALEN); + fip->map_dest = 0; + } + fip->flogi_oxid = FC_XID_UNKNOWN; + mutex_unlock(&fip->ctlr_mutex); + fc_fcoe_set_mac(fr_cb(fp)->granted_mac, fh->fh_d_id); + } else if (op == ELS_FLOGI && fh->fh_r_ctl == FC_RCTL_ELS_REQ && sa) { + /* + * Save source MAC for point-to-point responses. + */ + mutex_lock(&fip->ctlr_mutex); + if (fip->state == FIP_ST_AUTO || fip->state == FIP_ST_NON_FIP) { + memcpy(fip->dest_addr, sa, ETH_ALEN); + fip->map_dest = 0; + if (fip->state == FIP_ST_AUTO) + LIBFCOE_FIP_DBG(fip, "received non-FIP FLOGI. " + "Setting non-FIP mode\n"); + fcoe_ctlr_set_state(fip, FIP_ST_NON_FIP); + } + mutex_unlock(&fip->ctlr_mutex); + } + return 0; +} +EXPORT_SYMBOL(fcoe_ctlr_recv_flogi); + +/** + * fcoe_wwn_from_mac() - Converts a 48-bit IEEE MAC address to a 64-bit FC WWN + * @mac: The MAC address to convert + * @scheme: The scheme to use when converting + * @port: The port indicator for converting + * + * Returns: u64 fc world wide name + */ +u64 fcoe_wwn_from_mac(unsigned char mac[ETH_ALEN], + unsigned int scheme, unsigned int port) +{ + u64 wwn; + u64 host_mac; + + /* The MAC is in NO, so flip only the low 48 bits */ + host_mac = ((u64) mac[0] << 40) | + ((u64) mac[1] << 32) | + ((u64) mac[2] << 24) | + ((u64) mac[3] << 16) | + ((u64) mac[4] << 8) | + (u64) mac[5]; + + WARN_ON(host_mac >= (1ULL << 48)); + wwn = host_mac | ((u64) scheme << 60); + switch (scheme) { + case 1: + WARN_ON(port != 0); + break; + case 2: + WARN_ON(port >= 0xfff); + wwn |= (u64) port << 48; + break; + default: + WARN_ON(1); + break; + } + + return wwn; +} +EXPORT_SYMBOL_GPL(fcoe_wwn_from_mac); + +/** + * fcoe_ctlr_rport() - return the fcoe_rport for a given fc_rport_priv + * @rdata: libfc remote port + */ +static inline struct fcoe_rport *fcoe_ctlr_rport(struct fc_rport_priv *rdata) +{ + return container_of(rdata, struct fcoe_rport, rdata); +} + +/** + * fcoe_ctlr_vn_send() - Send a FIP VN2VN Probe Request or Reply. + * @fip: The FCoE controller + * @sub: sub-opcode for probe request, reply, or advertisement. + * @dest: The destination Ethernet MAC address + * @min_len: minimum size of the Ethernet payload to be sent + */ +static void fcoe_ctlr_vn_send(struct fcoe_ctlr *fip, + enum fip_vn2vn_subcode sub, + const u8 *dest, size_t min_len) +{ + struct sk_buff *skb; + struct fip_vn2vn_probe_frame { + struct ethhdr eth; + struct fip_header fip; + struct fip_mac_desc mac; + struct fip_wwn_desc wwnn; + struct fip_vn_desc vn; + } __packed * frame; + struct fip_fc4_feat *ff; + struct fip_size_desc *size; + u32 fcp_feat; + size_t len; + size_t dlen; + + len = sizeof(*frame); + dlen = 0; + if (sub == FIP_SC_VN_CLAIM_NOTIFY || sub == FIP_SC_VN_CLAIM_REP) { + dlen = sizeof(struct fip_fc4_feat) + + sizeof(struct fip_size_desc); + len += dlen; + } + dlen += sizeof(frame->mac) + sizeof(frame->wwnn) + sizeof(frame->vn); + len = max(len, min_len + sizeof(struct ethhdr)); + + skb = dev_alloc_skb(len); + if (!skb) + return; + + frame = (struct fip_vn2vn_probe_frame *)skb->data; + memset(frame, 0, len); + memcpy(frame->eth.h_dest, dest, ETH_ALEN); + + if (sub == FIP_SC_VN_BEACON) { + hton24(frame->eth.h_source, FIP_VN_FC_MAP); + hton24(frame->eth.h_source + 3, fip->port_id); + } else { + memcpy(frame->eth.h_source, fip->ctl_src_addr, ETH_ALEN); + } + frame->eth.h_proto = htons(ETH_P_FIP); + + frame->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER); + frame->fip.fip_op = htons(FIP_OP_VN2VN); + frame->fip.fip_subcode = sub; + frame->fip.fip_dl_len = htons(dlen / FIP_BPW); + + frame->mac.fd_desc.fip_dtype = FIP_DT_MAC; + frame->mac.fd_desc.fip_dlen = sizeof(frame->mac) / FIP_BPW; + memcpy(frame->mac.fd_mac, fip->ctl_src_addr, ETH_ALEN); + + frame->wwnn.fd_desc.fip_dtype = FIP_DT_NAME; + frame->wwnn.fd_desc.fip_dlen = sizeof(frame->wwnn) / FIP_BPW; + put_unaligned_be64(fip->lp->wwnn, &frame->wwnn.fd_wwn); + + frame->vn.fd_desc.fip_dtype = FIP_DT_VN_ID; + frame->vn.fd_desc.fip_dlen = sizeof(frame->vn) / FIP_BPW; + hton24(frame->vn.fd_mac, FIP_VN_FC_MAP); + hton24(frame->vn.fd_mac + 3, fip->port_id); + hton24(frame->vn.fd_fc_id, fip->port_id); + put_unaligned_be64(fip->lp->wwpn, &frame->vn.fd_wwpn); + + /* + * For claims, add FC-4 features. + * TBD: Add interface to get fc-4 types and features from libfc. + */ + if (sub == FIP_SC_VN_CLAIM_NOTIFY || sub == FIP_SC_VN_CLAIM_REP) { + ff = (struct fip_fc4_feat *)(frame + 1); + ff->fd_desc.fip_dtype = FIP_DT_FC4F; + ff->fd_desc.fip_dlen = sizeof(*ff) / FIP_BPW; + ff->fd_fts = fip->lp->fcts; + + fcp_feat = 0; + if (fip->lp->service_params & FCP_SPPF_INIT_FCN) + fcp_feat |= FCP_FEAT_INIT; + if (fip->lp->service_params & FCP_SPPF_TARG_FCN) + fcp_feat |= FCP_FEAT_TARG; + fcp_feat <<= (FC_TYPE_FCP * 4) % 32; + ff->fd_ff.fd_feat[FC_TYPE_FCP * 4 / 32] = htonl(fcp_feat); + + size = (struct fip_size_desc *)(ff + 1); + size->fd_desc.fip_dtype = FIP_DT_FCOE_SIZE; + size->fd_desc.fip_dlen = sizeof(*size) / FIP_BPW; + size->fd_size = htons(fcoe_ctlr_fcoe_size(fip)); + } + + skb_put(skb, len); + skb->protocol = htons(ETH_P_FIP); + skb->priority = fip->priority; + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + + fip->send(fip, skb); +} + +/** + * fcoe_ctlr_vn_rport_callback - Event handler for rport events. + * @lport: The lport which is receiving the event + * @rdata: remote port private data + * @event: The event that occurred + * + * Locking Note: The rport lock must not be held when calling this function. + */ +static void fcoe_ctlr_vn_rport_callback(struct fc_lport *lport, + struct fc_rport_priv *rdata, + enum fc_rport_event event) +{ + struct fcoe_ctlr *fip = lport->disc.priv; + struct fcoe_rport *frport = fcoe_ctlr_rport(rdata); + + LIBFCOE_FIP_DBG(fip, "vn_rport_callback %x event %d\n", + rdata->ids.port_id, event); + + mutex_lock(&fip->ctlr_mutex); + switch (event) { + case RPORT_EV_READY: + frport->login_count = 0; + break; + case RPORT_EV_LOGO: + case RPORT_EV_FAILED: + case RPORT_EV_STOP: + frport->login_count++; + if (frport->login_count > FCOE_CTLR_VN2VN_LOGIN_LIMIT) { + LIBFCOE_FIP_DBG(fip, + "rport FLOGI limited port_id %6.6x\n", + rdata->ids.port_id); + fc_rport_logoff(rdata); + } + break; + default: + break; + } + mutex_unlock(&fip->ctlr_mutex); +} + +static struct fc_rport_operations fcoe_ctlr_vn_rport_ops = { + .event_callback = fcoe_ctlr_vn_rport_callback, +}; + +/** + * fcoe_ctlr_disc_stop_locked() - stop discovery in VN2VN mode + * @lport: The local port + * + * Called with ctlr_mutex held. + */ +static void fcoe_ctlr_disc_stop_locked(struct fc_lport *lport) +{ + struct fc_rport_priv *rdata; + + mutex_lock(&lport->disc.disc_mutex); + list_for_each_entry_rcu(rdata, &lport->disc.rports, peers) { + if (kref_get_unless_zero(&rdata->kref)) { + fc_rport_logoff(rdata); + kref_put(&rdata->kref, fc_rport_destroy); + } + } + lport->disc.disc_callback = NULL; + mutex_unlock(&lport->disc.disc_mutex); +} + +/** + * fcoe_ctlr_disc_stop() - stop discovery in VN2VN mode + * @lport: The local port + * + * Called through the local port template for discovery. + * Called without the ctlr_mutex held. + */ +static void fcoe_ctlr_disc_stop(struct fc_lport *lport) +{ + struct fcoe_ctlr *fip = lport->disc.priv; + + mutex_lock(&fip->ctlr_mutex); + fcoe_ctlr_disc_stop_locked(lport); + mutex_unlock(&fip->ctlr_mutex); +} + +/** + * fcoe_ctlr_disc_stop_final() - stop discovery for shutdown in VN2VN mode + * @lport: The local port + * + * Called through the local port template for discovery. + * Called without the ctlr_mutex held. + */ +static void fcoe_ctlr_disc_stop_final(struct fc_lport *lport) +{ + fcoe_ctlr_disc_stop(lport); + fc_rport_flush_queue(); + synchronize_rcu(); +} + +/** + * fcoe_ctlr_vn_restart() - VN2VN probe restart with new port_id + * @fip: The FCoE controller + * + * Called with fcoe_ctlr lock held. + */ +static void fcoe_ctlr_vn_restart(struct fcoe_ctlr *fip) +{ + unsigned long wait; + u32 port_id; + + fcoe_ctlr_disc_stop_locked(fip->lp); + + /* + * Get proposed port ID. + * If this is the first try after link up, use any previous port_id. + * If there was none, use the low bits of the port_name. + * On subsequent tries, get the next random one. + * Don't use reserved IDs, use another non-zero value, just as random. + */ + port_id = fip->port_id; + if (fip->probe_tries) + port_id = prandom_u32_state(&fip->rnd_state) & 0xffff; + else if (!port_id) + port_id = fip->lp->wwpn & 0xffff; + if (!port_id || port_id == 0xffff) + port_id = 1; + fip->port_id = port_id; + + if (fip->probe_tries < FIP_VN_RLIM_COUNT) { + fip->probe_tries++; + wait = prandom_u32() % FIP_VN_PROBE_WAIT; + } else + wait = FIP_VN_RLIM_INT; + mod_timer(&fip->timer, jiffies + msecs_to_jiffies(wait)); + fcoe_ctlr_set_state(fip, FIP_ST_VNMP_START); +} + +/** + * fcoe_ctlr_vn_start() - Start in VN2VN mode + * @fip: The FCoE controller + * + * Called with fcoe_ctlr lock held. + */ +static void fcoe_ctlr_vn_start(struct fcoe_ctlr *fip) +{ + fip->probe_tries = 0; + prandom_seed_state(&fip->rnd_state, fip->lp->wwpn); + fcoe_ctlr_vn_restart(fip); +} + +/** + * fcoe_ctlr_vn_parse - parse probe request or response + * @fip: The FCoE controller + * @skb: incoming packet + * @frport: parsed FCoE rport from the probe request + * + * Returns non-zero error number on error. + * Does not consume the packet. + */ +static int fcoe_ctlr_vn_parse(struct fcoe_ctlr *fip, + struct sk_buff *skb, + struct fcoe_rport *frport) +{ + struct fip_header *fiph; + struct fip_desc *desc = NULL; + struct fip_mac_desc *macd = NULL; + struct fip_wwn_desc *wwn = NULL; + struct fip_vn_desc *vn = NULL; + struct fip_size_desc *size = NULL; + size_t rlen; + size_t dlen; + u32 desc_mask = 0; + u32 dtype; + u8 sub; + + fiph = (struct fip_header *)skb->data; + frport->flags = ntohs(fiph->fip_flags); + + sub = fiph->fip_subcode; + switch (sub) { + case FIP_SC_VN_PROBE_REQ: + case FIP_SC_VN_PROBE_REP: + case FIP_SC_VN_BEACON: + desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME) | + BIT(FIP_DT_VN_ID); + break; + case FIP_SC_VN_CLAIM_NOTIFY: + case FIP_SC_VN_CLAIM_REP: + desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME) | + BIT(FIP_DT_VN_ID) | BIT(FIP_DT_FC4F) | + BIT(FIP_DT_FCOE_SIZE); + break; + default: + LIBFCOE_FIP_DBG(fip, "vn_parse unknown subcode %u\n", sub); + return -EINVAL; + } + + rlen = ntohs(fiph->fip_dl_len) * 4; + if (rlen + sizeof(*fiph) > skb->len) + return -EINVAL; + + desc = (struct fip_desc *)(fiph + 1); + while (rlen > 0) { + dlen = desc->fip_dlen * FIP_BPW; + if (dlen < sizeof(*desc) || dlen > rlen) + return -EINVAL; + + dtype = desc->fip_dtype; + if (dtype < 32) { + if (!(desc_mask & BIT(dtype))) { + LIBFCOE_FIP_DBG(fip, + "unexpected or duplicated desc " + "desc type %u in " + "FIP VN2VN subtype %u\n", + dtype, sub); + return -EINVAL; + } + desc_mask &= ~BIT(dtype); + } + + switch (dtype) { + case FIP_DT_MAC: + if (dlen != sizeof(struct fip_mac_desc)) + goto len_err; + macd = (struct fip_mac_desc *)desc; + if (!is_valid_ether_addr(macd->fd_mac)) { + LIBFCOE_FIP_DBG(fip, + "Invalid MAC addr %pM in FIP VN2VN\n", + macd->fd_mac); + return -EINVAL; + } + memcpy(frport->enode_mac, macd->fd_mac, ETH_ALEN); + break; + case FIP_DT_NAME: + if (dlen != sizeof(struct fip_wwn_desc)) + goto len_err; + wwn = (struct fip_wwn_desc *)desc; + frport->rdata.ids.node_name = + get_unaligned_be64(&wwn->fd_wwn); + break; + case FIP_DT_VN_ID: + if (dlen != sizeof(struct fip_vn_desc)) + goto len_err; + vn = (struct fip_vn_desc *)desc; + memcpy(frport->vn_mac, vn->fd_mac, ETH_ALEN); + frport->rdata.ids.port_id = ntoh24(vn->fd_fc_id); + frport->rdata.ids.port_name = + get_unaligned_be64(&vn->fd_wwpn); + break; + case FIP_DT_FC4F: + if (dlen != sizeof(struct fip_fc4_feat)) + goto len_err; + break; + case FIP_DT_FCOE_SIZE: + if (dlen != sizeof(struct fip_size_desc)) + goto len_err; + size = (struct fip_size_desc *)desc; + frport->fcoe_len = ntohs(size->fd_size); + break; + default: + LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x " + "in FIP probe\n", dtype); + /* standard says ignore unknown descriptors >= 128 */ + if (dtype < FIP_DT_NON_CRITICAL) + return -EINVAL; + break; + } + desc = (struct fip_desc *)((char *)desc + dlen); + rlen -= dlen; + } + return 0; + +len_err: + LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n", + dtype, dlen); + return -EINVAL; +} + +/** + * fcoe_ctlr_vn_send_claim() - send multicast FIP VN2VN Claim Notification. + * @fip: The FCoE controller + * + * Called with ctlr_mutex held. + */ +static void fcoe_ctlr_vn_send_claim(struct fcoe_ctlr *fip) +{ + fcoe_ctlr_vn_send(fip, FIP_SC_VN_CLAIM_NOTIFY, fcoe_all_vn2vn, 0); + fip->sol_time = jiffies; +} + +/** + * fcoe_ctlr_vn_probe_req() - handle incoming VN2VN probe request. + * @fip: The FCoE controller + * @frport: parsed FCoE rport from the probe request + * + * Called with ctlr_mutex held. + */ +static void fcoe_ctlr_vn_probe_req(struct fcoe_ctlr *fip, + struct fcoe_rport *frport) +{ + if (frport->rdata.ids.port_id != fip->port_id) + return; + + switch (fip->state) { + case FIP_ST_VNMP_CLAIM: + case FIP_ST_VNMP_UP: + LIBFCOE_FIP_DBG(fip, "vn_probe_req: send reply, state %x\n", + fip->state); + fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REP, + frport->enode_mac, 0); + break; + case FIP_ST_VNMP_PROBE1: + case FIP_ST_VNMP_PROBE2: + /* + * Decide whether to reply to the Probe. + * Our selected address is never a "recorded" one, so + * only reply if our WWPN is greater and the + * Probe's REC bit is not set. + * If we don't reply, we will change our address. + */ + if (fip->lp->wwpn > frport->rdata.ids.port_name && + !(frport->flags & FIP_FL_REC_OR_P2P)) { + LIBFCOE_FIP_DBG(fip, "vn_probe_req: " + "port_id collision\n"); + fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REP, + frport->enode_mac, 0); + break; + } + fallthrough; + case FIP_ST_VNMP_START: + LIBFCOE_FIP_DBG(fip, "vn_probe_req: " + "restart VN2VN negotiation\n"); + fcoe_ctlr_vn_restart(fip); + break; + default: + LIBFCOE_FIP_DBG(fip, "vn_probe_req: ignore state %x\n", + fip->state); + break; + } +} + +/** + * fcoe_ctlr_vn_probe_reply() - handle incoming VN2VN probe reply. + * @fip: The FCoE controller + * @frport: parsed FCoE rport from the probe request + * + * Called with ctlr_mutex held. + */ +static void fcoe_ctlr_vn_probe_reply(struct fcoe_ctlr *fip, + struct fcoe_rport *frport) +{ + if (frport->rdata.ids.port_id != fip->port_id) + return; + switch (fip->state) { + case FIP_ST_VNMP_START: + case FIP_ST_VNMP_PROBE1: + case FIP_ST_VNMP_PROBE2: + case FIP_ST_VNMP_CLAIM: + LIBFCOE_FIP_DBG(fip, "vn_probe_reply: restart state %x\n", + fip->state); + fcoe_ctlr_vn_restart(fip); + break; + case FIP_ST_VNMP_UP: + LIBFCOE_FIP_DBG(fip, "vn_probe_reply: send claim notify\n"); + fcoe_ctlr_vn_send_claim(fip); + break; + default: + break; + } +} + +/** + * fcoe_ctlr_vn_add() - Add a VN2VN entry to the list, based on a claim reply. + * @fip: The FCoE controller + * @new: newly-parsed FCoE rport as a template for new rdata + * + * Called with ctlr_mutex held. + */ +static void fcoe_ctlr_vn_add(struct fcoe_ctlr *fip, struct fcoe_rport *new) +{ + struct fc_lport *lport = fip->lp; + struct fc_rport_priv *rdata; + struct fc_rport_identifiers *ids; + struct fcoe_rport *frport; + u32 port_id; + + port_id = new->rdata.ids.port_id; + if (port_id == fip->port_id) + return; + + mutex_lock(&lport->disc.disc_mutex); + rdata = fc_rport_create(lport, port_id); + if (!rdata) { + mutex_unlock(&lport->disc.disc_mutex); + return; + } + mutex_lock(&rdata->rp_mutex); + mutex_unlock(&lport->disc.disc_mutex); + + rdata->ops = &fcoe_ctlr_vn_rport_ops; + rdata->disc_id = lport->disc.disc_id; + + ids = &rdata->ids; + if ((ids->port_name != -1 && + ids->port_name != new->rdata.ids.port_name) || + (ids->node_name != -1 && + ids->node_name != new->rdata.ids.node_name)) { + mutex_unlock(&rdata->rp_mutex); + LIBFCOE_FIP_DBG(fip, "vn_add rport logoff %6.6x\n", port_id); + fc_rport_logoff(rdata); + mutex_lock(&rdata->rp_mutex); + } + ids->port_name = new->rdata.ids.port_name; + ids->node_name = new->rdata.ids.node_name; + mutex_unlock(&rdata->rp_mutex); + + frport = fcoe_ctlr_rport(rdata); + LIBFCOE_FIP_DBG(fip, "vn_add rport %6.6x %s state %d\n", + port_id, frport->fcoe_len ? "old" : "new", + rdata->rp_state); + frport->fcoe_len = new->fcoe_len; + frport->flags = new->flags; + frport->login_count = new->login_count; + memcpy(frport->enode_mac, new->enode_mac, ETH_ALEN); + memcpy(frport->vn_mac, new->vn_mac, ETH_ALEN); + frport->time = 0; +} + +/** + * fcoe_ctlr_vn_lookup() - Find VN remote port's MAC address + * @fip: The FCoE controller + * @port_id: The port_id of the remote VN_node + * @mac: buffer which will hold the VN_NODE destination MAC address, if found. + * + * Returns non-zero error if no remote port found. + */ +static int fcoe_ctlr_vn_lookup(struct fcoe_ctlr *fip, u32 port_id, u8 *mac) +{ + struct fc_lport *lport = fip->lp; + struct fc_rport_priv *rdata; + struct fcoe_rport *frport; + int ret = -1; + + rdata = fc_rport_lookup(lport, port_id); + if (rdata) { + frport = fcoe_ctlr_rport(rdata); + memcpy(mac, frport->enode_mac, ETH_ALEN); + ret = 0; + kref_put(&rdata->kref, fc_rport_destroy); + } + return ret; +} + +/** + * fcoe_ctlr_vn_claim_notify() - handle received FIP VN2VN Claim Notification + * @fip: The FCoE controller + * @new: newly-parsed FCoE rport as a template for new rdata + * + * Called with ctlr_mutex held. + */ +static void fcoe_ctlr_vn_claim_notify(struct fcoe_ctlr *fip, + struct fcoe_rport *new) +{ + if (new->flags & FIP_FL_REC_OR_P2P) { + LIBFCOE_FIP_DBG(fip, "send probe req for P2P/REC\n"); + fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0); + return; + } + switch (fip->state) { + case FIP_ST_VNMP_START: + case FIP_ST_VNMP_PROBE1: + case FIP_ST_VNMP_PROBE2: + if (new->rdata.ids.port_id == fip->port_id) { + LIBFCOE_FIP_DBG(fip, "vn_claim_notify: " + "restart, state %d\n", + fip->state); + fcoe_ctlr_vn_restart(fip); + } + break; + case FIP_ST_VNMP_CLAIM: + case FIP_ST_VNMP_UP: + if (new->rdata.ids.port_id == fip->port_id) { + if (new->rdata.ids.port_name > fip->lp->wwpn) { + LIBFCOE_FIP_DBG(fip, "vn_claim_notify: " + "restart, port_id collision\n"); + fcoe_ctlr_vn_restart(fip); + break; + } + LIBFCOE_FIP_DBG(fip, "vn_claim_notify: " + "send claim notify\n"); + fcoe_ctlr_vn_send_claim(fip); + break; + } + LIBFCOE_FIP_DBG(fip, "vn_claim_notify: send reply to %x\n", + new->rdata.ids.port_id); + fcoe_ctlr_vn_send(fip, FIP_SC_VN_CLAIM_REP, new->enode_mac, + min((u32)new->fcoe_len, + fcoe_ctlr_fcoe_size(fip))); + fcoe_ctlr_vn_add(fip, new); + break; + default: + LIBFCOE_FIP_DBG(fip, "vn_claim_notify: " + "ignoring claim from %x\n", + new->rdata.ids.port_id); + break; + } +} + +/** + * fcoe_ctlr_vn_claim_resp() - handle received Claim Response + * @fip: The FCoE controller that received the frame + * @new: newly-parsed FCoE rport from the Claim Response + * + * Called with ctlr_mutex held. + */ +static void fcoe_ctlr_vn_claim_resp(struct fcoe_ctlr *fip, + struct fcoe_rport *new) +{ + LIBFCOE_FIP_DBG(fip, "claim resp from from rport %x - state %s\n", + new->rdata.ids.port_id, fcoe_ctlr_state(fip->state)); + if (fip->state == FIP_ST_VNMP_UP || fip->state == FIP_ST_VNMP_CLAIM) + fcoe_ctlr_vn_add(fip, new); +} + +/** + * fcoe_ctlr_vn_beacon() - handle received beacon. + * @fip: The FCoE controller that received the frame + * @new: newly-parsed FCoE rport from the Beacon + * + * Called with ctlr_mutex held. + */ +static void fcoe_ctlr_vn_beacon(struct fcoe_ctlr *fip, + struct fcoe_rport *new) +{ + struct fc_lport *lport = fip->lp; + struct fc_rport_priv *rdata; + struct fcoe_rport *frport; + + if (new->flags & FIP_FL_REC_OR_P2P) { + LIBFCOE_FIP_DBG(fip, "p2p beacon while in vn2vn mode\n"); + fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0); + return; + } + rdata = fc_rport_lookup(lport, new->rdata.ids.port_id); + if (rdata) { + if (rdata->ids.node_name == new->rdata.ids.node_name && + rdata->ids.port_name == new->rdata.ids.port_name) { + frport = fcoe_ctlr_rport(rdata); + + LIBFCOE_FIP_DBG(fip, "beacon from rport %x\n", + rdata->ids.port_id); + if (!frport->time && fip->state == FIP_ST_VNMP_UP) { + LIBFCOE_FIP_DBG(fip, "beacon expired " + "for rport %x\n", + rdata->ids.port_id); + fc_rport_login(rdata); + } + frport->time = jiffies; + } + kref_put(&rdata->kref, fc_rport_destroy); + return; + } + if (fip->state != FIP_ST_VNMP_UP) + return; + + /* + * Beacon from a new neighbor. + * Send a claim notify if one hasn't been sent recently. + * Don't add the neighbor yet. + */ + LIBFCOE_FIP_DBG(fip, "beacon from new rport %x. sending claim notify\n", + new->rdata.ids.port_id); + if (time_after(jiffies, + fip->sol_time + msecs_to_jiffies(FIP_VN_ANN_WAIT))) + fcoe_ctlr_vn_send_claim(fip); +} + +/** + * fcoe_ctlr_vn_age() - Check for VN_ports without recent beacons + * @fip: The FCoE controller + * + * Called with ctlr_mutex held. + * Called only in state FIP_ST_VNMP_UP. + * Returns the soonest time for next age-out or a time far in the future. + */ +static unsigned long fcoe_ctlr_vn_age(struct fcoe_ctlr *fip) +{ + struct fc_lport *lport = fip->lp; + struct fc_rport_priv *rdata; + struct fcoe_rport *frport; + unsigned long next_time; + unsigned long deadline; + + next_time = jiffies + msecs_to_jiffies(FIP_VN_BEACON_INT * 10); + mutex_lock(&lport->disc.disc_mutex); + list_for_each_entry_rcu(rdata, &lport->disc.rports, peers) { + if (!kref_get_unless_zero(&rdata->kref)) + continue; + frport = fcoe_ctlr_rport(rdata); + if (!frport->time) { + kref_put(&rdata->kref, fc_rport_destroy); + continue; + } + deadline = frport->time + + msecs_to_jiffies(FIP_VN_BEACON_INT * 25 / 10); + if (time_after_eq(jiffies, deadline)) { + frport->time = 0; + LIBFCOE_FIP_DBG(fip, + "port %16.16llx fc_id %6.6x beacon expired\n", + rdata->ids.port_name, rdata->ids.port_id); + fc_rport_logoff(rdata); + } else if (time_before(deadline, next_time)) + next_time = deadline; + kref_put(&rdata->kref, fc_rport_destroy); + } + mutex_unlock(&lport->disc.disc_mutex); + return next_time; +} + +/** + * fcoe_ctlr_vn_recv() - Receive a FIP frame + * @fip: The FCoE controller that received the frame + * @skb: The received FIP frame + * + * Returns non-zero if the frame is dropped. + * Always consumes the frame. + */ +static int fcoe_ctlr_vn_recv(struct fcoe_ctlr *fip, struct sk_buff *skb) +{ + struct fip_header *fiph; + enum fip_vn2vn_subcode sub; + struct fcoe_rport frport = { }; + int rc, vlan_id = 0; + + fiph = (struct fip_header *)skb->data; + sub = fiph->fip_subcode; + + if (fip->lp->vlan) + vlan_id = skb_vlan_tag_get_id(skb); + + if (vlan_id && vlan_id != fip->lp->vlan) { + LIBFCOE_FIP_DBG(fip, "vn_recv drop frame sub %x vlan %d\n", + sub, vlan_id); + rc = -EAGAIN; + goto drop; + } + + rc = fcoe_ctlr_vn_parse(fip, skb, &frport); + if (rc) { + LIBFCOE_FIP_DBG(fip, "vn_recv vn_parse error %d\n", rc); + goto drop; + } + + mutex_lock(&fip->ctlr_mutex); + switch (sub) { + case FIP_SC_VN_PROBE_REQ: + fcoe_ctlr_vn_probe_req(fip, &frport); + break; + case FIP_SC_VN_PROBE_REP: + fcoe_ctlr_vn_probe_reply(fip, &frport); + break; + case FIP_SC_VN_CLAIM_NOTIFY: + fcoe_ctlr_vn_claim_notify(fip, &frport); + break; + case FIP_SC_VN_CLAIM_REP: + fcoe_ctlr_vn_claim_resp(fip, &frport); + break; + case FIP_SC_VN_BEACON: + fcoe_ctlr_vn_beacon(fip, &frport); + break; + default: + LIBFCOE_FIP_DBG(fip, "vn_recv unknown subcode %d\n", sub); + rc = -1; + break; + } + mutex_unlock(&fip->ctlr_mutex); +drop: + kfree_skb(skb); + return rc; +} + +/** + * fcoe_ctlr_vlan_parse - parse vlan discovery request or response + * @fip: The FCoE controller + * @skb: incoming packet + * @frport: parsed FCoE rport from the probe request + * + * Returns non-zero error number on error. + * Does not consume the packet. + */ +static int fcoe_ctlr_vlan_parse(struct fcoe_ctlr *fip, + struct sk_buff *skb, + struct fcoe_rport *frport) +{ + struct fip_header *fiph; + struct fip_desc *desc = NULL; + struct fip_mac_desc *macd = NULL; + struct fip_wwn_desc *wwn = NULL; + size_t rlen; + size_t dlen; + u32 desc_mask = 0; + u32 dtype; + u8 sub; + + fiph = (struct fip_header *)skb->data; + frport->flags = ntohs(fiph->fip_flags); + + sub = fiph->fip_subcode; + switch (sub) { + case FIP_SC_VL_REQ: + desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME); + break; + default: + LIBFCOE_FIP_DBG(fip, "vn_parse unknown subcode %u\n", sub); + return -EINVAL; + } + + rlen = ntohs(fiph->fip_dl_len) * 4; + if (rlen + sizeof(*fiph) > skb->len) + return -EINVAL; + + desc = (struct fip_desc *)(fiph + 1); + while (rlen > 0) { + dlen = desc->fip_dlen * FIP_BPW; + if (dlen < sizeof(*desc) || dlen > rlen) + return -EINVAL; + + dtype = desc->fip_dtype; + if (dtype < 32) { + if (!(desc_mask & BIT(dtype))) { + LIBFCOE_FIP_DBG(fip, + "unexpected or duplicated desc " + "desc type %u in " + "FIP VN2VN subtype %u\n", + dtype, sub); + return -EINVAL; + } + desc_mask &= ~BIT(dtype); + } + + switch (dtype) { + case FIP_DT_MAC: + if (dlen != sizeof(struct fip_mac_desc)) + goto len_err; + macd = (struct fip_mac_desc *)desc; + if (!is_valid_ether_addr(macd->fd_mac)) { + LIBFCOE_FIP_DBG(fip, + "Invalid MAC addr %pM in FIP VN2VN\n", + macd->fd_mac); + return -EINVAL; + } + memcpy(frport->enode_mac, macd->fd_mac, ETH_ALEN); + break; + case FIP_DT_NAME: + if (dlen != sizeof(struct fip_wwn_desc)) + goto len_err; + wwn = (struct fip_wwn_desc *)desc; + frport->rdata.ids.node_name = + get_unaligned_be64(&wwn->fd_wwn); + break; + default: + LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x " + "in FIP probe\n", dtype); + /* standard says ignore unknown descriptors >= 128 */ + if (dtype < FIP_DT_NON_CRITICAL) + return -EINVAL; + break; + } + desc = (struct fip_desc *)((char *)desc + dlen); + rlen -= dlen; + } + return 0; + +len_err: + LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n", + dtype, dlen); + return -EINVAL; +} + +/** + * fcoe_ctlr_vlan_send() - Send a FIP VLAN Notification + * @fip: The FCoE controller + * @sub: sub-opcode for vlan notification or vn2vn vlan notification + * @dest: The destination Ethernet MAC address + */ +static void fcoe_ctlr_vlan_send(struct fcoe_ctlr *fip, + enum fip_vlan_subcode sub, + const u8 *dest) +{ + struct sk_buff *skb; + struct fip_vlan_notify_frame { + struct ethhdr eth; + struct fip_header fip; + struct fip_mac_desc mac; + struct fip_vlan_desc vlan; + } __packed * frame; + size_t len; + size_t dlen; + + len = sizeof(*frame); + dlen = sizeof(frame->mac) + sizeof(frame->vlan); + len = max(len, sizeof(struct ethhdr)); + + skb = dev_alloc_skb(len); + if (!skb) + return; + + LIBFCOE_FIP_DBG(fip, "fip %s vlan notification, vlan %d\n", + fip->mode == FIP_MODE_VN2VN ? "vn2vn" : "fcf", + fip->lp->vlan); + + frame = (struct fip_vlan_notify_frame *)skb->data; + memset(frame, 0, len); + memcpy(frame->eth.h_dest, dest, ETH_ALEN); + + memcpy(frame->eth.h_source, fip->ctl_src_addr, ETH_ALEN); + frame->eth.h_proto = htons(ETH_P_FIP); + + frame->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER); + frame->fip.fip_op = htons(FIP_OP_VLAN); + frame->fip.fip_subcode = sub; + frame->fip.fip_dl_len = htons(dlen / FIP_BPW); + + frame->mac.fd_desc.fip_dtype = FIP_DT_MAC; + frame->mac.fd_desc.fip_dlen = sizeof(frame->mac) / FIP_BPW; + memcpy(frame->mac.fd_mac, fip->ctl_src_addr, ETH_ALEN); + + frame->vlan.fd_desc.fip_dtype = FIP_DT_VLAN; + frame->vlan.fd_desc.fip_dlen = sizeof(frame->vlan) / FIP_BPW; + put_unaligned_be16(fip->lp->vlan, &frame->vlan.fd_vlan); + + skb_put(skb, len); + skb->protocol = htons(ETH_P_FIP); + skb->priority = fip->priority; + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + + fip->send(fip, skb); +} + +/** + * fcoe_ctlr_vlan_disk_reply() - send FIP VLAN Discovery Notification. + * @fip: The FCoE controller + * @frport: The newly-parsed FCoE rport from the Discovery Request + * + * Called with ctlr_mutex held. + */ +static void fcoe_ctlr_vlan_disc_reply(struct fcoe_ctlr *fip, + struct fcoe_rport *frport) +{ + enum fip_vlan_subcode sub = FIP_SC_VL_NOTE; + + if (fip->mode == FIP_MODE_VN2VN) + sub = FIP_SC_VL_VN2VN_NOTE; + + fcoe_ctlr_vlan_send(fip, sub, frport->enode_mac); +} + +/** + * fcoe_ctlr_vlan_recv - vlan request receive handler for VN2VN mode. + * @fip: The FCoE controller + * @skb: The received FIP packet + */ +static int fcoe_ctlr_vlan_recv(struct fcoe_ctlr *fip, struct sk_buff *skb) +{ + struct fip_header *fiph; + enum fip_vlan_subcode sub; + struct fcoe_rport frport = { }; + int rc; + + fiph = (struct fip_header *)skb->data; + sub = fiph->fip_subcode; + rc = fcoe_ctlr_vlan_parse(fip, skb, &frport); + if (rc) { + LIBFCOE_FIP_DBG(fip, "vlan_recv vlan_parse error %d\n", rc); + goto drop; + } + mutex_lock(&fip->ctlr_mutex); + if (sub == FIP_SC_VL_REQ) + fcoe_ctlr_vlan_disc_reply(fip, &frport); + mutex_unlock(&fip->ctlr_mutex); + +drop: + kfree_skb(skb); + return rc; +} + +/** + * fcoe_ctlr_disc_recv - discovery receive handler for VN2VN mode. + * @lport: The local port + * @fp: The received frame + * + * This should never be called since we don't see RSCNs or other + * fabric-generated ELSes. + */ +static void fcoe_ctlr_disc_recv(struct fc_lport *lport, struct fc_frame *fp) +{ + struct fc_seq_els_data rjt_data; + + rjt_data.reason = ELS_RJT_UNSUP; + rjt_data.explan = ELS_EXPL_NONE; + fc_seq_els_rsp_send(fp, ELS_LS_RJT, &rjt_data); + fc_frame_free(fp); +} + +/* + * fcoe_ctlr_disc_start - start discovery for VN2VN mode. + * + * This sets a flag indicating that remote ports should be created + * and started for the peers we discover. We use the disc_callback + * pointer as that flag. Peers already discovered are created here. + * + * The lport lock is held during this call. The callback must be done + * later, without holding either the lport or discovery locks. + * The fcoe_ctlr lock may also be held during this call. + */ +static void fcoe_ctlr_disc_start(void (*callback)(struct fc_lport *, + enum fc_disc_event), + struct fc_lport *lport) +{ + struct fc_disc *disc = &lport->disc; + struct fcoe_ctlr *fip = disc->priv; + + mutex_lock(&disc->disc_mutex); + disc->disc_callback = callback; + disc->disc_id = (disc->disc_id + 2) | 1; + disc->pending = 1; + schedule_work(&fip->timer_work); + mutex_unlock(&disc->disc_mutex); +} + +/** + * fcoe_ctlr_vn_disc() - report FIP VN_port discovery results after claim state. + * @fip: The FCoE controller + * + * Starts the FLOGI and PLOGI login process to each discovered rport for which + * we've received at least one beacon. + * Performs the discovery complete callback. + */ +static void fcoe_ctlr_vn_disc(struct fcoe_ctlr *fip) +{ + struct fc_lport *lport = fip->lp; + struct fc_disc *disc = &lport->disc; + struct fc_rport_priv *rdata; + struct fcoe_rport *frport; + void (*callback)(struct fc_lport *, enum fc_disc_event); + + mutex_lock(&disc->disc_mutex); + callback = disc->pending ? disc->disc_callback : NULL; + disc->pending = 0; + list_for_each_entry_rcu(rdata, &disc->rports, peers) { + if (!kref_get_unless_zero(&rdata->kref)) + continue; + frport = fcoe_ctlr_rport(rdata); + if (frport->time) + fc_rport_login(rdata); + kref_put(&rdata->kref, fc_rport_destroy); + } + mutex_unlock(&disc->disc_mutex); + if (callback) + callback(lport, DISC_EV_SUCCESS); +} + +/** + * fcoe_ctlr_vn_timeout - timer work function for VN2VN mode. + * @fip: The FCoE controller + */ +static void fcoe_ctlr_vn_timeout(struct fcoe_ctlr *fip) +{ + unsigned long next_time; + u8 mac[ETH_ALEN]; + u32 new_port_id = 0; + + mutex_lock(&fip->ctlr_mutex); + switch (fip->state) { + case FIP_ST_VNMP_START: + fcoe_ctlr_set_state(fip, FIP_ST_VNMP_PROBE1); + LIBFCOE_FIP_DBG(fip, "vn_timeout: send 1st probe request\n"); + fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0); + next_time = jiffies + msecs_to_jiffies(FIP_VN_PROBE_WAIT); + break; + case FIP_ST_VNMP_PROBE1: + fcoe_ctlr_set_state(fip, FIP_ST_VNMP_PROBE2); + LIBFCOE_FIP_DBG(fip, "vn_timeout: send 2nd probe request\n"); + fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0); + next_time = jiffies + msecs_to_jiffies(FIP_VN_ANN_WAIT); + break; + case FIP_ST_VNMP_PROBE2: + fcoe_ctlr_set_state(fip, FIP_ST_VNMP_CLAIM); + new_port_id = fip->port_id; + hton24(mac, FIP_VN_FC_MAP); + hton24(mac + 3, new_port_id); + fcoe_ctlr_map_dest(fip); + fip->update_mac(fip->lp, mac); + LIBFCOE_FIP_DBG(fip, "vn_timeout: send claim notify\n"); + fcoe_ctlr_vn_send_claim(fip); + next_time = jiffies + msecs_to_jiffies(FIP_VN_ANN_WAIT); + break; + case FIP_ST_VNMP_CLAIM: + /* + * This may be invoked either by starting discovery so don't + * go to the next state unless it's been long enough. + */ + next_time = fip->sol_time + msecs_to_jiffies(FIP_VN_ANN_WAIT); + if (time_after_eq(jiffies, next_time)) { + fcoe_ctlr_set_state(fip, FIP_ST_VNMP_UP); + LIBFCOE_FIP_DBG(fip, "vn_timeout: send vn2vn beacon\n"); + fcoe_ctlr_vn_send(fip, FIP_SC_VN_BEACON, + fcoe_all_vn2vn, 0); + next_time = jiffies + msecs_to_jiffies(FIP_VN_ANN_WAIT); + fip->port_ka_time = next_time; + } + fcoe_ctlr_vn_disc(fip); + break; + case FIP_ST_VNMP_UP: + next_time = fcoe_ctlr_vn_age(fip); + if (time_after_eq(jiffies, fip->port_ka_time)) { + LIBFCOE_FIP_DBG(fip, "vn_timeout: send vn2vn beacon\n"); + fcoe_ctlr_vn_send(fip, FIP_SC_VN_BEACON, + fcoe_all_vn2vn, 0); + fip->port_ka_time = jiffies + + msecs_to_jiffies(FIP_VN_BEACON_INT + + (prandom_u32() % FIP_VN_BEACON_FUZZ)); + } + if (time_before(fip->port_ka_time, next_time)) + next_time = fip->port_ka_time; + break; + case FIP_ST_LINK_WAIT: + goto unlock; + default: + WARN(1, "unexpected state %d\n", fip->state); + goto unlock; + } + mod_timer(&fip->timer, next_time); +unlock: + mutex_unlock(&fip->ctlr_mutex); + + /* If port ID is new, notify local port after dropping ctlr_mutex */ + if (new_port_id) + fc_lport_set_local_id(fip->lp, new_port_id); +} + +/** + * fcoe_ctlr_mode_set() - Set or reset the ctlr's mode + * @lport: The local port to be (re)configured + * @fip: The FCoE controller whose mode is changing + * @fip_mode: The new fip mode + * + * Note that the we shouldn't be changing the libfc discovery settings + * (fc_disc_config) while an lport is going through the libfc state + * machine. The mode can only be changed when a fcoe_ctlr device is + * disabled, so that should ensure that this routine is only called + * when nothing is happening. + */ +static void fcoe_ctlr_mode_set(struct fc_lport *lport, struct fcoe_ctlr *fip, + enum fip_mode fip_mode) +{ + void *priv; + + WARN_ON(lport->state != LPORT_ST_RESET && + lport->state != LPORT_ST_DISABLED); + + if (fip_mode == FIP_MODE_VN2VN) { + lport->rport_priv_size = sizeof(struct fcoe_rport); + lport->point_to_multipoint = 1; + lport->tt.disc_recv_req = fcoe_ctlr_disc_recv; + lport->tt.disc_start = fcoe_ctlr_disc_start; + lport->tt.disc_stop = fcoe_ctlr_disc_stop; + lport->tt.disc_stop_final = fcoe_ctlr_disc_stop_final; + priv = fip; + } else { + lport->rport_priv_size = 0; + lport->point_to_multipoint = 0; + lport->tt.disc_recv_req = NULL; + lport->tt.disc_start = NULL; + lport->tt.disc_stop = NULL; + lport->tt.disc_stop_final = NULL; + priv = lport; + } + + fc_disc_config(lport, priv); +} + +/** + * fcoe_libfc_config() - Sets up libfc related properties for local port + * @lport: The local port to configure libfc for + * @fip: The FCoE controller in use by the local port + * @tt: The libfc function template + * @init_fcp: If non-zero, the FCP portion of libfc should be initialized + * + * Returns : 0 for success + */ +int fcoe_libfc_config(struct fc_lport *lport, struct fcoe_ctlr *fip, + const struct libfc_function_template *tt, int init_fcp) +{ + /* Set the function pointers set by the LLDD */ + memcpy(&lport->tt, tt, sizeof(*tt)); + if (init_fcp && fc_fcp_init(lport)) + return -ENOMEM; + fc_exch_init(lport); + fc_elsct_init(lport); + fc_lport_init(lport); + fc_disc_init(lport); + fcoe_ctlr_mode_set(lport, fip, fip->mode); + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_libfc_config); + +void fcoe_fcf_get_selected(struct fcoe_fcf_device *fcf_dev) +{ + struct fcoe_ctlr_device *ctlr_dev = fcoe_fcf_dev_to_ctlr_dev(fcf_dev); + struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr_dev); + struct fcoe_fcf *fcf; + + mutex_lock(&fip->ctlr_mutex); + mutex_lock(&ctlr_dev->lock); + + fcf = fcoe_fcf_device_priv(fcf_dev); + if (fcf) + fcf_dev->selected = (fcf == fip->sel_fcf) ? 1 : 0; + else + fcf_dev->selected = 0; + + mutex_unlock(&ctlr_dev->lock); + mutex_unlock(&fip->ctlr_mutex); +} +EXPORT_SYMBOL(fcoe_fcf_get_selected); + +void fcoe_ctlr_set_fip_mode(struct fcoe_ctlr_device *ctlr_dev) +{ + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + struct fc_lport *lport = ctlr->lp; + + mutex_lock(&ctlr->ctlr_mutex); + switch (ctlr_dev->mode) { + case FIP_CONN_TYPE_VN2VN: + ctlr->mode = FIP_MODE_VN2VN; + break; + case FIP_CONN_TYPE_FABRIC: + default: + ctlr->mode = FIP_MODE_FABRIC; + break; + } + + mutex_unlock(&ctlr->ctlr_mutex); + + fcoe_ctlr_mode_set(lport, ctlr, ctlr->mode); +} +EXPORT_SYMBOL(fcoe_ctlr_set_fip_mode); diff --git a/drivers/scsi/fcoe/fcoe_sysfs.c b/drivers/scsi/fcoe/fcoe_sysfs.c new file mode 100644 index 000000000..68d8027d5 --- /dev/null +++ b/drivers/scsi/fcoe/fcoe_sysfs.c @@ -0,0 +1,1066 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2011 - 2012 Intel Corporation. All rights reserved. + * + * Maintained at www.Open-FCoE.org + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/etherdevice.h> +#include <linux/ctype.h> + +#include <scsi/fcoe_sysfs.h> +#include <scsi/libfcoe.h> + +/* + * OK to include local libfcoe.h for debug_logging, but cannot include + * <scsi/libfcoe.h> otherwise non-netdev based fcoe solutions would have + * have to include more than fcoe_sysfs.h. + */ +#include "libfcoe.h" + +static atomic_t ctlr_num; +static atomic_t fcf_num; + +/* + * fcoe_fcf_dev_loss_tmo: the default number of seconds that fcoe sysfs + * should insulate the loss of a fcf. + */ +static unsigned int fcoe_fcf_dev_loss_tmo = 1800; /* seconds */ + +module_param_named(fcf_dev_loss_tmo, fcoe_fcf_dev_loss_tmo, + uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(fcf_dev_loss_tmo, + "Maximum number of seconds that libfcoe should" + " insulate the loss of a fcf. Once this value is" + " exceeded, the fcf is removed."); + +/* + * These are used by the fcoe_*_show_function routines, they + * are intentionally placed in the .c file as they're not intended + * for use throughout the code. + */ +#define fcoe_ctlr_id(x) \ + ((x)->id) +#define fcoe_ctlr_work_q_name(x) \ + ((x)->work_q_name) +#define fcoe_ctlr_work_q(x) \ + ((x)->work_q) +#define fcoe_ctlr_devloss_work_q_name(x) \ + ((x)->devloss_work_q_name) +#define fcoe_ctlr_devloss_work_q(x) \ + ((x)->devloss_work_q) +#define fcoe_ctlr_mode(x) \ + ((x)->mode) +#define fcoe_ctlr_fcf_dev_loss_tmo(x) \ + ((x)->fcf_dev_loss_tmo) +#define fcoe_ctlr_link_fail(x) \ + ((x)->lesb.lesb_link_fail) +#define fcoe_ctlr_vlink_fail(x) \ + ((x)->lesb.lesb_vlink_fail) +#define fcoe_ctlr_miss_fka(x) \ + ((x)->lesb.lesb_miss_fka) +#define fcoe_ctlr_symb_err(x) \ + ((x)->lesb.lesb_symb_err) +#define fcoe_ctlr_err_block(x) \ + ((x)->lesb.lesb_err_block) +#define fcoe_ctlr_fcs_error(x) \ + ((x)->lesb.lesb_fcs_error) +#define fcoe_ctlr_enabled(x) \ + ((x)->enabled) +#define fcoe_fcf_state(x) \ + ((x)->state) +#define fcoe_fcf_fabric_name(x) \ + ((x)->fabric_name) +#define fcoe_fcf_switch_name(x) \ + ((x)->switch_name) +#define fcoe_fcf_fc_map(x) \ + ((x)->fc_map) +#define fcoe_fcf_vfid(x) \ + ((x)->vfid) +#define fcoe_fcf_mac(x) \ + ((x)->mac) +#define fcoe_fcf_priority(x) \ + ((x)->priority) +#define fcoe_fcf_fka_period(x) \ + ((x)->fka_period) +#define fcoe_fcf_dev_loss_tmo(x) \ + ((x)->dev_loss_tmo) +#define fcoe_fcf_selected(x) \ + ((x)->selected) +#define fcoe_fcf_vlan_id(x) \ + ((x)->vlan_id) + +/* + * dev_loss_tmo attribute + */ +static int fcoe_str_to_dev_loss(const char *buf, unsigned long *val) +{ + int ret; + + ret = kstrtoul(buf, 0, val); + if (ret) + return -EINVAL; + /* + * Check for overflow; dev_loss_tmo is u32 + */ + if (*val > UINT_MAX) + return -EINVAL; + + return 0; +} + +static int fcoe_fcf_set_dev_loss_tmo(struct fcoe_fcf_device *fcf, + unsigned long val) +{ + if ((fcf->state == FCOE_FCF_STATE_UNKNOWN) || + (fcf->state == FCOE_FCF_STATE_DISCONNECTED) || + (fcf->state == FCOE_FCF_STATE_DELETED)) + return -EBUSY; + /* + * Check for overflow; dev_loss_tmo is u32 + */ + if (val > UINT_MAX) + return -EINVAL; + + fcoe_fcf_dev_loss_tmo(fcf) = val; + return 0; +} + +#define FCOE_DEVICE_ATTR(_prefix, _name, _mode, _show, _store) \ +struct device_attribute device_attr_fcoe_##_prefix##_##_name = \ + __ATTR(_name, _mode, _show, _store) + +#define fcoe_ctlr_show_function(field, format_string, sz, cast) \ +static ssize_t show_fcoe_ctlr_device_##field(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); \ + if (ctlr->f->get_fcoe_ctlr_##field) \ + ctlr->f->get_fcoe_ctlr_##field(ctlr); \ + return snprintf(buf, sz, format_string, \ + cast fcoe_ctlr_##field(ctlr)); \ +} + +#define fcoe_fcf_show_function(field, format_string, sz, cast) \ +static ssize_t show_fcoe_fcf_device_##field(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); \ + struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); \ + if (ctlr->f->get_fcoe_fcf_##field) \ + ctlr->f->get_fcoe_fcf_##field(fcf); \ + return snprintf(buf, sz, format_string, \ + cast fcoe_fcf_##field(fcf)); \ +} + +#define fcoe_ctlr_private_show_function(field, format_string, sz, cast) \ +static ssize_t show_fcoe_ctlr_device_##field(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); \ + return snprintf(buf, sz, format_string, cast fcoe_ctlr_##field(ctlr)); \ +} + +#define fcoe_fcf_private_show_function(field, format_string, sz, cast) \ +static ssize_t show_fcoe_fcf_device_##field(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); \ + return snprintf(buf, sz, format_string, cast fcoe_fcf_##field(fcf)); \ +} + +#define fcoe_ctlr_private_rd_attr(field, format_string, sz) \ + fcoe_ctlr_private_show_function(field, format_string, sz, ) \ + static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO, \ + show_fcoe_ctlr_device_##field, NULL) + +#define fcoe_ctlr_rd_attr(field, format_string, sz) \ + fcoe_ctlr_show_function(field, format_string, sz, ) \ + static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO, \ + show_fcoe_ctlr_device_##field, NULL) + +#define fcoe_fcf_rd_attr(field, format_string, sz) \ + fcoe_fcf_show_function(field, format_string, sz, ) \ + static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO, \ + show_fcoe_fcf_device_##field, NULL) + +#define fcoe_fcf_private_rd_attr(field, format_string, sz) \ + fcoe_fcf_private_show_function(field, format_string, sz, ) \ + static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO, \ + show_fcoe_fcf_device_##field, NULL) + +#define fcoe_ctlr_private_rd_attr_cast(field, format_string, sz, cast) \ + fcoe_ctlr_private_show_function(field, format_string, sz, (cast)) \ + static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO, \ + show_fcoe_ctlr_device_##field, NULL) + +#define fcoe_fcf_private_rd_attr_cast(field, format_string, sz, cast) \ + fcoe_fcf_private_show_function(field, format_string, sz, (cast)) \ + static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO, \ + show_fcoe_fcf_device_##field, NULL) + +#define fcoe_enum_name_search(title, table_type, table) \ +static const char *get_fcoe_##title##_name(enum table_type table_key) \ +{ \ + if (table_key < 0 || table_key >= ARRAY_SIZE(table)) \ + return NULL; \ + return table[table_key]; \ +} + +static char *fip_conn_type_names[] = { + [ FIP_CONN_TYPE_UNKNOWN ] = "Unknown", + [ FIP_CONN_TYPE_FABRIC ] = "Fabric", + [ FIP_CONN_TYPE_VN2VN ] = "VN2VN", +}; +fcoe_enum_name_search(ctlr_mode, fip_conn_type, fip_conn_type_names) + +static enum fip_conn_type fcoe_parse_mode(const char *buf) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fip_conn_type_names); i++) { + if (strcasecmp(buf, fip_conn_type_names[i]) == 0) + return i; + } + + return FIP_CONN_TYPE_UNKNOWN; +} + +static char *fcf_state_names[] = { + [ FCOE_FCF_STATE_UNKNOWN ] = "Unknown", + [ FCOE_FCF_STATE_DISCONNECTED ] = "Disconnected", + [ FCOE_FCF_STATE_CONNECTED ] = "Connected", +}; +fcoe_enum_name_search(fcf_state, fcf_state, fcf_state_names) +#define FCOE_FCF_STATE_MAX_NAMELEN 50 + +static ssize_t show_fcf_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); + const char *name; + name = get_fcoe_fcf_state_name(fcf->state); + if (!name) + return -EINVAL; + return snprintf(buf, FCOE_FCF_STATE_MAX_NAMELEN, "%s\n", name); +} +static FCOE_DEVICE_ATTR(fcf, state, S_IRUGO, show_fcf_state, NULL); + +#define FCOE_MAX_MODENAME_LEN 20 +static ssize_t show_ctlr_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + const char *name; + + name = get_fcoe_ctlr_mode_name(ctlr->mode); + if (!name) + return -EINVAL; + return snprintf(buf, FCOE_MAX_MODENAME_LEN, + "%s\n", name); +} + +static ssize_t store_ctlr_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + char mode[FCOE_MAX_MODENAME_LEN + 1]; + + if (count > FCOE_MAX_MODENAME_LEN) + return -EINVAL; + + strncpy(mode, buf, count); + + if (mode[count - 1] == '\n') + mode[count - 1] = '\0'; + else + mode[count] = '\0'; + + switch (ctlr->enabled) { + case FCOE_CTLR_ENABLED: + LIBFCOE_SYSFS_DBG(ctlr, "Cannot change mode when enabled.\n"); + return -EBUSY; + case FCOE_CTLR_DISABLED: + if (!ctlr->f->set_fcoe_ctlr_mode) { + LIBFCOE_SYSFS_DBG(ctlr, + "Mode change not supported by LLD.\n"); + return -ENOTSUPP; + } + + ctlr->mode = fcoe_parse_mode(mode); + if (ctlr->mode == FIP_CONN_TYPE_UNKNOWN) { + LIBFCOE_SYSFS_DBG(ctlr, "Unknown mode %s provided.\n", + buf); + return -EINVAL; + } + + ctlr->f->set_fcoe_ctlr_mode(ctlr); + LIBFCOE_SYSFS_DBG(ctlr, "Mode changed to %s.\n", buf); + + return count; + case FCOE_CTLR_UNUSED: + default: + LIBFCOE_SYSFS_DBG(ctlr, "Mode change not supported.\n"); + return -ENOTSUPP; + }; +} + +static FCOE_DEVICE_ATTR(ctlr, mode, S_IRUGO | S_IWUSR, + show_ctlr_mode, store_ctlr_mode); + +static ssize_t store_ctlr_enabled(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + bool enabled; + int rc; + + if (*buf == '1') + enabled = true; + else if (*buf == '0') + enabled = false; + else + return -EINVAL; + + switch (ctlr->enabled) { + case FCOE_CTLR_ENABLED: + if (enabled) + return count; + ctlr->enabled = FCOE_CTLR_DISABLED; + break; + case FCOE_CTLR_DISABLED: + if (!enabled) + return count; + ctlr->enabled = FCOE_CTLR_ENABLED; + break; + case FCOE_CTLR_UNUSED: + return -ENOTSUPP; + }; + + rc = ctlr->f->set_fcoe_ctlr_enabled(ctlr); + if (rc) + return rc; + + return count; +} + +static char *ctlr_enabled_state_names[] = { + [ FCOE_CTLR_ENABLED ] = "1", + [ FCOE_CTLR_DISABLED ] = "0", +}; +fcoe_enum_name_search(ctlr_enabled_state, ctlr_enabled_state, + ctlr_enabled_state_names) +#define FCOE_CTLR_ENABLED_MAX_NAMELEN 50 + +static ssize_t show_ctlr_enabled_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + const char *name; + + name = get_fcoe_ctlr_enabled_state_name(ctlr->enabled); + if (!name) + return -EINVAL; + return snprintf(buf, FCOE_CTLR_ENABLED_MAX_NAMELEN, + "%s\n", name); +} + +static FCOE_DEVICE_ATTR(ctlr, enabled, S_IRUGO | S_IWUSR, + show_ctlr_enabled_state, + store_ctlr_enabled); + +static ssize_t store_ctlr_fip_resp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr); + + mutex_lock(&fip->ctlr_mutex); + if ((buf[1] == '\0') || ((buf[1] == '\n') && (buf[2] == '\0'))) { + if (buf[0] == '1') { + fip->fip_resp = 1; + mutex_unlock(&fip->ctlr_mutex); + return count; + } + if (buf[0] == '0') { + fip->fip_resp = 0; + mutex_unlock(&fip->ctlr_mutex); + return count; + } + } + mutex_unlock(&fip->ctlr_mutex); + return -EINVAL; +} + +static ssize_t show_ctlr_fip_resp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr); + + return sprintf(buf, "%d\n", fip->fip_resp ? 1 : 0); +} + +static FCOE_DEVICE_ATTR(ctlr, fip_vlan_responder, S_IRUGO | S_IWUSR, + show_ctlr_fip_resp, + store_ctlr_fip_resp); + +static ssize_t +fcoe_ctlr_var_store(u32 *var, const char *buf, size_t count) +{ + int err; + unsigned long v; + + err = kstrtoul(buf, 10, &v); + if (err || v > UINT_MAX) + return -EINVAL; + + *var = v; + + return count; +} + +static ssize_t store_ctlr_r_a_tov(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcoe_ctlr_device *ctlr_dev = dev_to_ctlr(dev); + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + + if (ctlr_dev->enabled == FCOE_CTLR_ENABLED) + return -EBUSY; + if (ctlr_dev->enabled == FCOE_CTLR_DISABLED) + return fcoe_ctlr_var_store(&ctlr->lp->r_a_tov, buf, count); + return -ENOTSUPP; +} + +static ssize_t show_ctlr_r_a_tov(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fcoe_ctlr_device *ctlr_dev = dev_to_ctlr(dev); + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + + return sprintf(buf, "%d\n", ctlr->lp->r_a_tov); +} + +static FCOE_DEVICE_ATTR(ctlr, r_a_tov, S_IRUGO | S_IWUSR, + show_ctlr_r_a_tov, store_ctlr_r_a_tov); + +static ssize_t store_ctlr_e_d_tov(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcoe_ctlr_device *ctlr_dev = dev_to_ctlr(dev); + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + + if (ctlr_dev->enabled == FCOE_CTLR_ENABLED) + return -EBUSY; + if (ctlr_dev->enabled == FCOE_CTLR_DISABLED) + return fcoe_ctlr_var_store(&ctlr->lp->e_d_tov, buf, count); + return -ENOTSUPP; +} + +static ssize_t show_ctlr_e_d_tov(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fcoe_ctlr_device *ctlr_dev = dev_to_ctlr(dev); + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + + return sprintf(buf, "%d\n", ctlr->lp->e_d_tov); +} + +static FCOE_DEVICE_ATTR(ctlr, e_d_tov, S_IRUGO | S_IWUSR, + show_ctlr_e_d_tov, store_ctlr_e_d_tov); + +static ssize_t +store_private_fcoe_ctlr_fcf_dev_loss_tmo(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + struct fcoe_fcf_device *fcf; + unsigned long val; + int rc; + + rc = fcoe_str_to_dev_loss(buf, &val); + if (rc) + return rc; + + fcoe_ctlr_fcf_dev_loss_tmo(ctlr) = val; + mutex_lock(&ctlr->lock); + list_for_each_entry(fcf, &ctlr->fcfs, peers) + fcoe_fcf_set_dev_loss_tmo(fcf, val); + mutex_unlock(&ctlr->lock); + return count; +} +fcoe_ctlr_private_show_function(fcf_dev_loss_tmo, "%d\n", 20, ); +static FCOE_DEVICE_ATTR(ctlr, fcf_dev_loss_tmo, S_IRUGO | S_IWUSR, + show_fcoe_ctlr_device_fcf_dev_loss_tmo, + store_private_fcoe_ctlr_fcf_dev_loss_tmo); + +/* Link Error Status Block (LESB) */ +fcoe_ctlr_rd_attr(link_fail, "%u\n", 20); +fcoe_ctlr_rd_attr(vlink_fail, "%u\n", 20); +fcoe_ctlr_rd_attr(miss_fka, "%u\n", 20); +fcoe_ctlr_rd_attr(symb_err, "%u\n", 20); +fcoe_ctlr_rd_attr(err_block, "%u\n", 20); +fcoe_ctlr_rd_attr(fcs_error, "%u\n", 20); + +fcoe_fcf_private_rd_attr_cast(fabric_name, "0x%llx\n", 20, unsigned long long); +fcoe_fcf_private_rd_attr_cast(switch_name, "0x%llx\n", 20, unsigned long long); +fcoe_fcf_private_rd_attr(priority, "%u\n", 20); +fcoe_fcf_private_rd_attr(fc_map, "0x%x\n", 20); +fcoe_fcf_private_rd_attr(vfid, "%u\n", 20); +fcoe_fcf_private_rd_attr(mac, "%pM\n", 20); +fcoe_fcf_private_rd_attr(fka_period, "%u\n", 20); +fcoe_fcf_rd_attr(selected, "%u\n", 20); +fcoe_fcf_rd_attr(vlan_id, "%u\n", 20); + +fcoe_fcf_private_show_function(dev_loss_tmo, "%d\n", 20, ) +static ssize_t +store_fcoe_fcf_dev_loss_tmo(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); + unsigned long val; + int rc; + + rc = fcoe_str_to_dev_loss(buf, &val); + if (rc) + return rc; + + rc = fcoe_fcf_set_dev_loss_tmo(fcf, val); + if (rc) + return rc; + return count; +} +static FCOE_DEVICE_ATTR(fcf, dev_loss_tmo, S_IRUGO | S_IWUSR, + show_fcoe_fcf_device_dev_loss_tmo, + store_fcoe_fcf_dev_loss_tmo); + +static struct attribute *fcoe_ctlr_lesb_attrs[] = { + &device_attr_fcoe_ctlr_link_fail.attr, + &device_attr_fcoe_ctlr_vlink_fail.attr, + &device_attr_fcoe_ctlr_miss_fka.attr, + &device_attr_fcoe_ctlr_symb_err.attr, + &device_attr_fcoe_ctlr_err_block.attr, + &device_attr_fcoe_ctlr_fcs_error.attr, + NULL, +}; + +static struct attribute_group fcoe_ctlr_lesb_attr_group = { + .name = "lesb", + .attrs = fcoe_ctlr_lesb_attrs, +}; + +static struct attribute *fcoe_ctlr_attrs[] = { + &device_attr_fcoe_ctlr_fip_vlan_responder.attr, + &device_attr_fcoe_ctlr_fcf_dev_loss_tmo.attr, + &device_attr_fcoe_ctlr_r_a_tov.attr, + &device_attr_fcoe_ctlr_e_d_tov.attr, + &device_attr_fcoe_ctlr_enabled.attr, + &device_attr_fcoe_ctlr_mode.attr, + NULL, +}; + +static struct attribute_group fcoe_ctlr_attr_group = { + .attrs = fcoe_ctlr_attrs, +}; + +static const struct attribute_group *fcoe_ctlr_attr_groups[] = { + &fcoe_ctlr_attr_group, + &fcoe_ctlr_lesb_attr_group, + NULL, +}; + +static struct attribute *fcoe_fcf_attrs[] = { + &device_attr_fcoe_fcf_fabric_name.attr, + &device_attr_fcoe_fcf_switch_name.attr, + &device_attr_fcoe_fcf_dev_loss_tmo.attr, + &device_attr_fcoe_fcf_fc_map.attr, + &device_attr_fcoe_fcf_vfid.attr, + &device_attr_fcoe_fcf_mac.attr, + &device_attr_fcoe_fcf_priority.attr, + &device_attr_fcoe_fcf_fka_period.attr, + &device_attr_fcoe_fcf_state.attr, + &device_attr_fcoe_fcf_selected.attr, + &device_attr_fcoe_fcf_vlan_id.attr, + NULL +}; + +static struct attribute_group fcoe_fcf_attr_group = { + .attrs = fcoe_fcf_attrs, +}; + +static const struct attribute_group *fcoe_fcf_attr_groups[] = { + &fcoe_fcf_attr_group, + NULL, +}; + +static struct bus_type fcoe_bus_type; + +static int fcoe_bus_match(struct device *dev, + struct device_driver *drv) +{ + if (dev->bus == &fcoe_bus_type) + return 1; + return 0; +} + +/** + * fcoe_ctlr_device_release() - Release the FIP ctlr memory + * @dev: Pointer to the FIP ctlr's embedded device + * + * Called when the last FIP ctlr reference is released. + */ +static void fcoe_ctlr_device_release(struct device *dev) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + kfree(ctlr); +} + +/** + * fcoe_fcf_device_release() - Release the FIP fcf memory + * @dev: Pointer to the fcf's embedded device + * + * Called when the last FIP fcf reference is released. + */ +static void fcoe_fcf_device_release(struct device *dev) +{ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); + kfree(fcf); +} + +static const struct device_type fcoe_ctlr_device_type = { + .name = "fcoe_ctlr", + .groups = fcoe_ctlr_attr_groups, + .release = fcoe_ctlr_device_release, +}; + +static const struct device_type fcoe_fcf_device_type = { + .name = "fcoe_fcf", + .groups = fcoe_fcf_attr_groups, + .release = fcoe_fcf_device_release, +}; + +static ssize_t ctlr_create_store(struct bus_type *bus, const char *buf, + size_t count) +{ + return fcoe_ctlr_create_store(bus, buf, count); +} +static BUS_ATTR_WO(ctlr_create); + +static ssize_t ctlr_destroy_store(struct bus_type *bus, const char *buf, + size_t count) +{ + return fcoe_ctlr_destroy_store(bus, buf, count); +} +static BUS_ATTR_WO(ctlr_destroy); + +static struct attribute *fcoe_bus_attrs[] = { + &bus_attr_ctlr_create.attr, + &bus_attr_ctlr_destroy.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fcoe_bus); + +static struct bus_type fcoe_bus_type = { + .name = "fcoe", + .match = &fcoe_bus_match, + .bus_groups = fcoe_bus_groups, +}; + +/** + * fcoe_ctlr_device_flush_work() - Flush a FIP ctlr's workqueue + * @ctlr: Pointer to the FIP ctlr whose workqueue is to be flushed + */ +static void fcoe_ctlr_device_flush_work(struct fcoe_ctlr_device *ctlr) +{ + if (!fcoe_ctlr_work_q(ctlr)) { + printk(KERN_ERR + "ERROR: FIP Ctlr '%d' attempted to flush work, " + "when no workqueue created.\n", ctlr->id); + dump_stack(); + return; + } + + flush_workqueue(fcoe_ctlr_work_q(ctlr)); +} + +/** + * fcoe_ctlr_device_queue_work() - Schedule work for a FIP ctlr's workqueue + * @ctlr: Pointer to the FIP ctlr who owns the devloss workqueue + * @work: Work to queue for execution + * + * Return value: + * 1 on success / 0 already queued / < 0 for error + */ +static int fcoe_ctlr_device_queue_work(struct fcoe_ctlr_device *ctlr, + struct work_struct *work) +{ + if (unlikely(!fcoe_ctlr_work_q(ctlr))) { + printk(KERN_ERR + "ERROR: FIP Ctlr '%d' attempted to queue work, " + "when no workqueue created.\n", ctlr->id); + dump_stack(); + + return -EINVAL; + } + + return queue_work(fcoe_ctlr_work_q(ctlr), work); +} + +/** + * fcoe_ctlr_device_flush_devloss() - Flush a FIP ctlr's devloss workqueue + * @ctlr: Pointer to FIP ctlr whose workqueue is to be flushed + */ +static void fcoe_ctlr_device_flush_devloss(struct fcoe_ctlr_device *ctlr) +{ + if (!fcoe_ctlr_devloss_work_q(ctlr)) { + printk(KERN_ERR + "ERROR: FIP Ctlr '%d' attempted to flush work, " + "when no workqueue created.\n", ctlr->id); + dump_stack(); + return; + } + + flush_workqueue(fcoe_ctlr_devloss_work_q(ctlr)); +} + +/** + * fcoe_ctlr_device_queue_devloss_work() - Schedule work for a FIP ctlr's devloss workqueue + * @ctlr: Pointer to the FIP ctlr who owns the devloss workqueue + * @work: Work to queue for execution + * @delay: jiffies to delay the work queuing + * + * Return value: + * 1 on success / 0 already queued / < 0 for error + */ +static int fcoe_ctlr_device_queue_devloss_work(struct fcoe_ctlr_device *ctlr, + struct delayed_work *work, + unsigned long delay) +{ + if (unlikely(!fcoe_ctlr_devloss_work_q(ctlr))) { + printk(KERN_ERR + "ERROR: FIP Ctlr '%d' attempted to queue work, " + "when no workqueue created.\n", ctlr->id); + dump_stack(); + + return -EINVAL; + } + + return queue_delayed_work(fcoe_ctlr_devloss_work_q(ctlr), work, delay); +} + +static int fcoe_fcf_device_match(struct fcoe_fcf_device *new, + struct fcoe_fcf_device *old) +{ + if (new->switch_name == old->switch_name && + new->fabric_name == old->fabric_name && + new->fc_map == old->fc_map && + ether_addr_equal(new->mac, old->mac)) + return 1; + return 0; +} + +/** + * fcoe_ctlr_device_add() - Add a FIP ctlr to sysfs + * @parent: The parent device to which the fcoe_ctlr instance + * should be attached + * @f: The LLD's FCoE sysfs function template pointer + * @priv_size: Size to be allocated with the fcoe_ctlr_device for the LLD + * + * This routine allocates a FIP ctlr object with some additional memory + * for the LLD. The FIP ctlr is initialized, added to sysfs and then + * attributes are added to it. + */ +struct fcoe_ctlr_device *fcoe_ctlr_device_add(struct device *parent, + struct fcoe_sysfs_function_template *f, + int priv_size) +{ + struct fcoe_ctlr_device *ctlr; + int error = 0; + + ctlr = kzalloc(sizeof(struct fcoe_ctlr_device) + priv_size, + GFP_KERNEL); + if (!ctlr) + goto out; + + ctlr->id = atomic_inc_return(&ctlr_num) - 1; + ctlr->f = f; + ctlr->mode = FIP_CONN_TYPE_FABRIC; + INIT_LIST_HEAD(&ctlr->fcfs); + mutex_init(&ctlr->lock); + ctlr->dev.parent = parent; + ctlr->dev.bus = &fcoe_bus_type; + ctlr->dev.type = &fcoe_ctlr_device_type; + + ctlr->fcf_dev_loss_tmo = fcoe_fcf_dev_loss_tmo; + + snprintf(ctlr->work_q_name, sizeof(ctlr->work_q_name), + "ctlr_wq_%d", ctlr->id); + ctlr->work_q = create_singlethread_workqueue( + ctlr->work_q_name); + if (!ctlr->work_q) + goto out_del; + + snprintf(ctlr->devloss_work_q_name, + sizeof(ctlr->devloss_work_q_name), + "ctlr_dl_wq_%d", ctlr->id); + ctlr->devloss_work_q = create_singlethread_workqueue( + ctlr->devloss_work_q_name); + if (!ctlr->devloss_work_q) + goto out_del_q; + + dev_set_name(&ctlr->dev, "ctlr_%d", ctlr->id); + error = device_register(&ctlr->dev); + if (error) { + destroy_workqueue(ctlr->devloss_work_q); + destroy_workqueue(ctlr->work_q); + put_device(&ctlr->dev); + return NULL; + } + + return ctlr; + +out_del_q: + destroy_workqueue(ctlr->work_q); + ctlr->work_q = NULL; +out_del: + kfree(ctlr); +out: + return NULL; +} +EXPORT_SYMBOL_GPL(fcoe_ctlr_device_add); + +/** + * fcoe_ctlr_device_delete() - Delete a FIP ctlr and its subtree from sysfs + * @ctlr: A pointer to the ctlr to be deleted + * + * Deletes a FIP ctlr and any fcfs attached + * to it. Deleting fcfs will cause their childen + * to be deleted as well. + * + * The ctlr is detached from sysfs and it's resources + * are freed (work q), but the memory is not freed + * until its last reference is released. + * + * This routine expects no locks to be held before + * calling. + * + * TODO: Currently there are no callbacks to clean up LLD data + * for a fcoe_fcf_device. LLDs must keep this in mind as they need + * to clean up each of their LLD data for all fcoe_fcf_device before + * calling fcoe_ctlr_device_delete. + */ +void fcoe_ctlr_device_delete(struct fcoe_ctlr_device *ctlr) +{ + struct fcoe_fcf_device *fcf, *next; + /* Remove any attached fcfs */ + mutex_lock(&ctlr->lock); + list_for_each_entry_safe(fcf, next, + &ctlr->fcfs, peers) { + list_del(&fcf->peers); + fcf->state = FCOE_FCF_STATE_DELETED; + fcoe_ctlr_device_queue_work(ctlr, &fcf->delete_work); + } + mutex_unlock(&ctlr->lock); + + fcoe_ctlr_device_flush_work(ctlr); + + destroy_workqueue(ctlr->devloss_work_q); + ctlr->devloss_work_q = NULL; + destroy_workqueue(ctlr->work_q); + ctlr->work_q = NULL; + + device_unregister(&ctlr->dev); +} +EXPORT_SYMBOL_GPL(fcoe_ctlr_device_delete); + +/** + * fcoe_fcf_device_final_delete() - Final delete routine + * @work: The FIP fcf's embedded work struct + * + * It is expected that the fcf has been removed from + * the FIP ctlr's list before calling this routine. + */ +static void fcoe_fcf_device_final_delete(struct work_struct *work) +{ + struct fcoe_fcf_device *fcf = + container_of(work, struct fcoe_fcf_device, delete_work); + struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); + + /* + * Cancel any outstanding timers. These should really exist + * only when rmmod'ing the LLDD and we're asking for + * immediate termination of the rports + */ + if (!cancel_delayed_work(&fcf->dev_loss_work)) + fcoe_ctlr_device_flush_devloss(ctlr); + + device_unregister(&fcf->dev); +} + +/** + * fip_timeout_deleted_fcf() - Delete a fcf when the devloss timer fires + * @work: The FIP fcf's embedded work struct + * + * Removes the fcf from the FIP ctlr's list of fcfs and + * queues the final deletion. + */ +static void fip_timeout_deleted_fcf(struct work_struct *work) +{ + struct fcoe_fcf_device *fcf = + container_of(work, struct fcoe_fcf_device, dev_loss_work.work); + struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); + + mutex_lock(&ctlr->lock); + + /* + * If the fcf is deleted or reconnected before the timer + * fires the devloss queue will be flushed, but the state will + * either be CONNECTED or DELETED. If that is the case we + * cancel deleting the fcf. + */ + if (fcf->state != FCOE_FCF_STATE_DISCONNECTED) + goto out; + + dev_printk(KERN_ERR, &fcf->dev, + "FIP fcf connection time out: removing fcf\n"); + + list_del(&fcf->peers); + fcf->state = FCOE_FCF_STATE_DELETED; + fcoe_ctlr_device_queue_work(ctlr, &fcf->delete_work); + +out: + mutex_unlock(&ctlr->lock); +} + +/** + * fcoe_fcf_device_delete() - Delete a FIP fcf + * @fcf: Pointer to the fcf which is to be deleted + * + * Queues the FIP fcf on the devloss workqueue + * + * Expects the ctlr_attrs mutex to be held for fcf + * state change. + */ +void fcoe_fcf_device_delete(struct fcoe_fcf_device *fcf) +{ + struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); + int timeout = fcf->dev_loss_tmo; + + if (fcf->state != FCOE_FCF_STATE_CONNECTED) + return; + + fcf->state = FCOE_FCF_STATE_DISCONNECTED; + + /* + * FCF will only be re-connected by the LLD calling + * fcoe_fcf_device_add, and it should be setting up + * priv then. + */ + fcf->priv = NULL; + + fcoe_ctlr_device_queue_devloss_work(ctlr, &fcf->dev_loss_work, + timeout * HZ); +} +EXPORT_SYMBOL_GPL(fcoe_fcf_device_delete); + +/** + * fcoe_fcf_device_add() - Add a FCoE sysfs fcoe_fcf_device to the system + * @ctlr: The fcoe_ctlr_device that will be the fcoe_fcf_device parent + * @new_fcf: A temporary FCF used for lookups on the current list of fcfs + * + * Expects to be called with the ctlr->lock held + */ +struct fcoe_fcf_device *fcoe_fcf_device_add(struct fcoe_ctlr_device *ctlr, + struct fcoe_fcf_device *new_fcf) +{ + struct fcoe_fcf_device *fcf; + int error = 0; + + list_for_each_entry(fcf, &ctlr->fcfs, peers) { + if (fcoe_fcf_device_match(new_fcf, fcf)) { + if (fcf->state == FCOE_FCF_STATE_CONNECTED) + return fcf; + + fcf->state = FCOE_FCF_STATE_CONNECTED; + + if (!cancel_delayed_work(&fcf->dev_loss_work)) + fcoe_ctlr_device_flush_devloss(ctlr); + + return fcf; + } + } + + fcf = kzalloc(sizeof(struct fcoe_fcf_device), GFP_ATOMIC); + if (unlikely(!fcf)) + goto out; + + INIT_WORK(&fcf->delete_work, fcoe_fcf_device_final_delete); + INIT_DELAYED_WORK(&fcf->dev_loss_work, fip_timeout_deleted_fcf); + + fcf->dev.parent = &ctlr->dev; + fcf->dev.bus = &fcoe_bus_type; + fcf->dev.type = &fcoe_fcf_device_type; + fcf->id = atomic_inc_return(&fcf_num) - 1; + fcf->state = FCOE_FCF_STATE_UNKNOWN; + + fcf->dev_loss_tmo = ctlr->fcf_dev_loss_tmo; + + dev_set_name(&fcf->dev, "fcf_%d", fcf->id); + + fcf->fabric_name = new_fcf->fabric_name; + fcf->switch_name = new_fcf->switch_name; + fcf->fc_map = new_fcf->fc_map; + fcf->vfid = new_fcf->vfid; + memcpy(fcf->mac, new_fcf->mac, ETH_ALEN); + fcf->priority = new_fcf->priority; + fcf->fka_period = new_fcf->fka_period; + fcf->selected = new_fcf->selected; + + error = device_register(&fcf->dev); + if (error) { + put_device(&fcf->dev); + goto out; + } + + fcf->state = FCOE_FCF_STATE_CONNECTED; + list_add_tail(&fcf->peers, &ctlr->fcfs); + + return fcf; + +out: + return NULL; +} +EXPORT_SYMBOL_GPL(fcoe_fcf_device_add); + +int __init fcoe_sysfs_setup(void) +{ + atomic_set(&ctlr_num, 0); + atomic_set(&fcf_num, 0); + + return bus_register(&fcoe_bus_type); +} + +void __exit fcoe_sysfs_teardown(void) +{ + bus_unregister(&fcoe_bus_type); +} diff --git a/drivers/scsi/fcoe/fcoe_transport.c b/drivers/scsi/fcoe/fcoe_transport.c new file mode 100644 index 000000000..6e187d0e7 --- /dev/null +++ b/drivers/scsi/fcoe/fcoe_transport.c @@ -0,0 +1,1058 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. + * + * Maintained at www.Open-FCoE.org + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/netdevice.h> +#include <linux/errno.h> +#include <linux/crc32.h> +#include <scsi/libfcoe.h> + +#include "libfcoe.h" + +MODULE_AUTHOR("Open-FCoE.org"); +MODULE_DESCRIPTION("FIP discovery protocol and FCoE transport for FCoE HBAs"); +MODULE_LICENSE("GPL v2"); + +static int fcoe_transport_create(const char *, const struct kernel_param *); +static int fcoe_transport_destroy(const char *, const struct kernel_param *); +static int fcoe_transport_show(char *buffer, const struct kernel_param *kp); +static struct fcoe_transport *fcoe_transport_lookup(struct net_device *device); +static struct fcoe_transport *fcoe_netdev_map_lookup(struct net_device *device); +static int fcoe_transport_enable(const char *, const struct kernel_param *); +static int fcoe_transport_disable(const char *, const struct kernel_param *); +static int libfcoe_device_notification(struct notifier_block *notifier, + ulong event, void *ptr); + +static LIST_HEAD(fcoe_transports); +static DEFINE_MUTEX(ft_mutex); +static LIST_HEAD(fcoe_netdevs); +static DEFINE_MUTEX(fn_mutex); + +unsigned int libfcoe_debug_logging; +module_param_named(debug_logging, libfcoe_debug_logging, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels"); + +module_param_call(show, NULL, fcoe_transport_show, NULL, S_IRUSR); +__MODULE_PARM_TYPE(show, "string"); +MODULE_PARM_DESC(show, " Show attached FCoE transports"); + +module_param_call(create, fcoe_transport_create, NULL, + (void *)FIP_MODE_FABRIC, S_IWUSR); +__MODULE_PARM_TYPE(create, "string"); +MODULE_PARM_DESC(create, " Creates fcoe instance on an ethernet interface"); + +module_param_call(create_vn2vn, fcoe_transport_create, NULL, + (void *)FIP_MODE_VN2VN, S_IWUSR); +__MODULE_PARM_TYPE(create_vn2vn, "string"); +MODULE_PARM_DESC(create_vn2vn, " Creates a VN_node to VN_node FCoE instance " + "on an Ethernet interface"); + +module_param_call(destroy, fcoe_transport_destroy, NULL, NULL, S_IWUSR); +__MODULE_PARM_TYPE(destroy, "string"); +MODULE_PARM_DESC(destroy, " Destroys fcoe instance on an ethernet interface"); + +module_param_call(enable, fcoe_transport_enable, NULL, NULL, S_IWUSR); +__MODULE_PARM_TYPE(enable, "string"); +MODULE_PARM_DESC(enable, " Enables fcoe on an ethernet interface."); + +module_param_call(disable, fcoe_transport_disable, NULL, NULL, S_IWUSR); +__MODULE_PARM_TYPE(disable, "string"); +MODULE_PARM_DESC(disable, " Disables fcoe on an ethernet interface."); + +/* notification function for packets from net device */ +static struct notifier_block libfcoe_notifier = { + .notifier_call = libfcoe_device_notification, +}; + +static const struct { + u32 fc_port_speed; +#define SPEED_2000 2000 +#define SPEED_4000 4000 +#define SPEED_8000 8000 +#define SPEED_16000 16000 +#define SPEED_32000 32000 + u32 eth_port_speed; +} fcoe_port_speed_mapping[] = { + { FC_PORTSPEED_1GBIT, SPEED_1000 }, + { FC_PORTSPEED_2GBIT, SPEED_2000 }, + { FC_PORTSPEED_4GBIT, SPEED_4000 }, + { FC_PORTSPEED_8GBIT, SPEED_8000 }, + { FC_PORTSPEED_10GBIT, SPEED_10000 }, + { FC_PORTSPEED_16GBIT, SPEED_16000 }, + { FC_PORTSPEED_20GBIT, SPEED_20000 }, + { FC_PORTSPEED_25GBIT, SPEED_25000 }, + { FC_PORTSPEED_32GBIT, SPEED_32000 }, + { FC_PORTSPEED_40GBIT, SPEED_40000 }, + { FC_PORTSPEED_50GBIT, SPEED_50000 }, + { FC_PORTSPEED_100GBIT, SPEED_100000 }, +}; + +static inline u32 eth2fc_speed(u32 eth_port_speed) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fcoe_port_speed_mapping); i++) { + if (fcoe_port_speed_mapping[i].eth_port_speed == eth_port_speed) + return fcoe_port_speed_mapping[i].fc_port_speed; + } + + return FC_PORTSPEED_UNKNOWN; +} + +/** + * fcoe_link_speed_update() - Update the supported and actual link speeds + * @lport: The local port to update speeds for + * + * Returns: 0 if the ethtool query was successful + * -1 if the ethtool query failed + */ +int fcoe_link_speed_update(struct fc_lport *lport) +{ + struct net_device *netdev = fcoe_get_netdev(lport); + struct ethtool_link_ksettings ecmd; + + if (!__ethtool_get_link_ksettings(netdev, &ecmd)) { + lport->link_supported_speeds &= ~(FC_PORTSPEED_1GBIT | + FC_PORTSPEED_10GBIT | + FC_PORTSPEED_20GBIT | + FC_PORTSPEED_40GBIT); + + if (ecmd.link_modes.supported[0] & ( + SUPPORTED_1000baseT_Half | + SUPPORTED_1000baseT_Full | + SUPPORTED_1000baseKX_Full)) + lport->link_supported_speeds |= FC_PORTSPEED_1GBIT; + + if (ecmd.link_modes.supported[0] & ( + SUPPORTED_10000baseT_Full | + SUPPORTED_10000baseKX4_Full | + SUPPORTED_10000baseKR_Full | + SUPPORTED_10000baseR_FEC)) + lport->link_supported_speeds |= FC_PORTSPEED_10GBIT; + + if (ecmd.link_modes.supported[0] & ( + SUPPORTED_20000baseMLD2_Full | + SUPPORTED_20000baseKR2_Full)) + lport->link_supported_speeds |= FC_PORTSPEED_20GBIT; + + if (ecmd.link_modes.supported[0] & ( + SUPPORTED_40000baseKR4_Full | + SUPPORTED_40000baseCR4_Full | + SUPPORTED_40000baseSR4_Full | + SUPPORTED_40000baseLR4_Full)) + lport->link_supported_speeds |= FC_PORTSPEED_40GBIT; + + lport->link_speed = eth2fc_speed(ecmd.base.speed); + return 0; + } + return -1; +} +EXPORT_SYMBOL_GPL(fcoe_link_speed_update); + +/** + * __fcoe_get_lesb() - Get the Link Error Status Block (LESB) for a given lport + * @lport: The local port to update speeds for + * @fc_lesb: Pointer to the LESB to be filled up + * @netdev: Pointer to the netdev that is associated with the lport + * + * Note, the Link Error Status Block (LESB) for FCoE is defined in FC-BB-6 + * Clause 7.11 in v1.04. + */ +void __fcoe_get_lesb(struct fc_lport *lport, + struct fc_els_lesb *fc_lesb, + struct net_device *netdev) +{ + unsigned int cpu; + u32 lfc, vlfc, mdac; + struct fc_stats *stats; + struct fcoe_fc_els_lesb *lesb; + struct rtnl_link_stats64 temp; + + lfc = 0; + vlfc = 0; + mdac = 0; + lesb = (struct fcoe_fc_els_lesb *)fc_lesb; + memset(lesb, 0, sizeof(*lesb)); + for_each_possible_cpu(cpu) { + stats = per_cpu_ptr(lport->stats, cpu); + lfc += stats->LinkFailureCount; + vlfc += stats->VLinkFailureCount; + mdac += stats->MissDiscAdvCount; + } + lesb->lesb_link_fail = htonl(lfc); + lesb->lesb_vlink_fail = htonl(vlfc); + lesb->lesb_miss_fka = htonl(mdac); + lesb->lesb_fcs_error = + htonl(dev_get_stats(netdev, &temp)->rx_crc_errors); +} +EXPORT_SYMBOL_GPL(__fcoe_get_lesb); + +/** + * fcoe_get_lesb() - Fill the FCoE Link Error Status Block + * @lport: the local port + * @fc_lesb: the link error status block + */ +void fcoe_get_lesb(struct fc_lport *lport, + struct fc_els_lesb *fc_lesb) +{ + struct net_device *netdev = fcoe_get_netdev(lport); + + __fcoe_get_lesb(lport, fc_lesb, netdev); +} +EXPORT_SYMBOL_GPL(fcoe_get_lesb); + +/** + * fcoe_ctlr_get_lesb() - Get the Link Error Status Block (LESB) for a given + * fcoe controller device + * @ctlr_dev: The given fcoe controller device + * + */ +void fcoe_ctlr_get_lesb(struct fcoe_ctlr_device *ctlr_dev) +{ + struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr_dev); + struct net_device *netdev = fcoe_get_netdev(fip->lp); + struct fc_els_lesb *fc_lesb; + + fc_lesb = (struct fc_els_lesb *)(&ctlr_dev->lesb); + __fcoe_get_lesb(fip->lp, fc_lesb, netdev); +} +EXPORT_SYMBOL_GPL(fcoe_ctlr_get_lesb); + +void fcoe_wwn_to_str(u64 wwn, char *buf, int len) +{ + u8 wwpn[8]; + + u64_to_wwn(wwn, wwpn); + snprintf(buf, len, "%02x%02x%02x%02x%02x%02x%02x%02x", + wwpn[0], wwpn[1], wwpn[2], wwpn[3], + wwpn[4], wwpn[5], wwpn[6], wwpn[7]); +} +EXPORT_SYMBOL_GPL(fcoe_wwn_to_str); + +/** + * fcoe_validate_vport_create() - Validate a vport before creating it + * @vport: NPIV port to be created + * + * This routine is meant to add validation for a vport before creating it + * via fcoe_vport_create(). + * Current validations are: + * - WWPN supplied is unique for given lport + */ +int fcoe_validate_vport_create(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; + int rc = 0; + char buf[32]; + + mutex_lock(&n_port->lp_mutex); + + fcoe_wwn_to_str(vport->port_name, buf, sizeof(buf)); + /* Check if the wwpn is not same as that of the lport */ + if (!memcmp(&n_port->wwpn, &vport->port_name, sizeof(u64))) { + LIBFCOE_TRANSPORT_DBG("vport WWPN 0x%s is same as that of the " + "base port WWPN\n", buf); + rc = -EINVAL; + goto out; + } + + /* Check if there is any existing vport with same wwpn */ + list_for_each_entry(vn_port, &n_port->vports, list) { + if (!memcmp(&vn_port->wwpn, &vport->port_name, sizeof(u64))) { + LIBFCOE_TRANSPORT_DBG("vport with given WWPN 0x%s " + "already exists\n", buf); + rc = -EINVAL; + break; + } + } +out: + mutex_unlock(&n_port->lp_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(fcoe_validate_vport_create); + +/** + * fcoe_get_wwn() - Get the world wide name from LLD if it supports it + * @netdev: the associated net device + * @wwn: the output WWN + * @type: the type of WWN (WWPN or WWNN) + * + * Returns: 0 for success + */ +int fcoe_get_wwn(struct net_device *netdev, u64 *wwn, int type) +{ + const struct net_device_ops *ops = netdev->netdev_ops; + + if (ops->ndo_fcoe_get_wwn) + return ops->ndo_fcoe_get_wwn(netdev, wwn, type); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(fcoe_get_wwn); + +/** + * fcoe_fc_crc() - Calculates the CRC for a given frame + * @fp: The frame to be checksumed + * + * This uses crc32() routine to calculate the CRC for a frame + * + * Return: The 32 bit CRC value + */ +u32 fcoe_fc_crc(struct fc_frame *fp) +{ + struct sk_buff *skb = fp_skb(fp); + skb_frag_t *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 = skb_frag_off(frag); + len = skb_frag_size(frag); + while (len > 0) { + clen = min(len, PAGE_SIZE - (off & ~PAGE_MASK)); + data = kmap_atomic( + skb_frag_page(frag) + (off >> PAGE_SHIFT)); + crc = crc32(crc, data + (off & ~PAGE_MASK), clen); + kunmap_atomic(data); + off += clen; + len -= clen; + } + } + return crc; +} +EXPORT_SYMBOL_GPL(fcoe_fc_crc); + +/** + * fcoe_start_io() - Start FCoE I/O + * @skb: The packet to be transmitted + * + * This routine is called from the net device to start transmitting + * FCoE packets. + * + * Returns: 0 for success + */ +int fcoe_start_io(struct sk_buff *skb) +{ + struct sk_buff *nskb; + int rc; + + nskb = skb_clone(skb, GFP_ATOMIC); + if (!nskb) + return -ENOMEM; + rc = dev_queue_xmit(nskb); + if (rc != 0) + return rc; + kfree_skb(skb); + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_start_io); + + +/** + * fcoe_clean_pending_queue() - Dequeue a skb and free it + * @lport: The local port to dequeue a skb on + */ +void fcoe_clean_pending_queue(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + struct sk_buff *skb; + + 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); +} +EXPORT_SYMBOL_GPL(fcoe_clean_pending_queue); + +/** + * fcoe_check_wait_queue() - Attempt to clear the transmit backlog + * @lport: The local port whose backlog is to be cleared + * @skb: The received FIP packet + * + * This empties the wait_queue, dequeues the head of the wait_queue queue + * and calls fcoe_start_io() for each packet. If all skb have been + * transmitted it returns the qlen. If an error occurs it restores + * wait_queue (to try again later) and returns -1. + * + * The wait_queue is used when the skb transmit fails. The failed skb + * will go in the wait_queue which will be emptied by the timer function or + * by the next skb transmit. + */ +void fcoe_check_wait_queue(struct fc_lport *lport, struct sk_buff *skb) +{ + struct fcoe_port *port = lport_priv(lport); + int rc; + + spin_lock_bh(&port->fcoe_pending_queue.lock); + + if (skb) + __skb_queue_tail(&port->fcoe_pending_queue, skb); + + if (port->fcoe_pending_queue_active) + goto out; + port->fcoe_pending_queue_active = 1; + + 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); + + spin_unlock_bh(&port->fcoe_pending_queue.lock); + rc = fcoe_start_io(skb); + spin_lock_bh(&port->fcoe_pending_queue.lock); + + if (rc) { + __skb_queue_head(&port->fcoe_pending_queue, skb); + /* undo temporary increment above */ + port->fcoe_pending_queue.qlen--; + break; + } + /* undo temporary increment above */ + port->fcoe_pending_queue.qlen--; + } + + if (port->fcoe_pending_queue.qlen < port->min_queue_depth) + lport->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 > port->max_queue_depth) + lport->qfull = 1; + spin_unlock_bh(&port->fcoe_pending_queue.lock); +} +EXPORT_SYMBOL_GPL(fcoe_check_wait_queue); + +/** + * fcoe_queue_timer() - The fcoe queue timer + * @t: Timer context use to obtain the FCoE port + * + * Calls fcoe_check_wait_queue on timeout + */ +void fcoe_queue_timer(struct timer_list *t) +{ + struct fcoe_port *port = from_timer(port, t, timer); + + fcoe_check_wait_queue(port->lport, NULL); +} +EXPORT_SYMBOL_GPL(fcoe_queue_timer); + +/** + * fcoe_get_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 + * @fps: The fcoe context + * + * This routine allocates a page for frame trailers. The page is re-used if + * there is enough room left on it for the current trailer. If there isn't + * enough buffer left a new page is allocated for the trailer. Reference to + * the page from this function as well as the skbs using the page fragments + * ensure that the page is freed at the appropriate time. + * + * Returns: 0 for success + */ +int fcoe_get_paged_crc_eof(struct sk_buff *skb, int tlen, + struct fcoe_percpu_s *fps) +{ + struct page *page; + + page = fps->crc_eof_page; + if (!page) { + page = alloc_page(GFP_ATOMIC); + if (!page) + 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); + } + + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_get_paged_crc_eof); + +/** + * fcoe_transport_lookup - find an fcoe transport that matches a netdev + * @netdev: The netdev to look for from all attached transports + * + * Returns : ptr to the fcoe transport that supports this netdev or NULL + * if not found. + * + * The ft_mutex should be held when this is called + */ +static struct fcoe_transport *fcoe_transport_lookup(struct net_device *netdev) +{ + struct fcoe_transport *ft = NULL; + + list_for_each_entry(ft, &fcoe_transports, list) + if (ft->match && ft->match(netdev)) + return ft; + return NULL; +} + +/** + * fcoe_transport_attach - Attaches an FCoE transport + * @ft: The fcoe transport to be attached + * + * Returns : 0 for success + */ +int fcoe_transport_attach(struct fcoe_transport *ft) +{ + int rc = 0; + + mutex_lock(&ft_mutex); + if (ft->attached) { + LIBFCOE_TRANSPORT_DBG("transport %s already attached\n", + ft->name); + rc = -EEXIST; + goto out_attach; + } + + /* Add default transport to the tail */ + if (strcmp(ft->name, FCOE_TRANSPORT_DEFAULT)) + list_add(&ft->list, &fcoe_transports); + else + list_add_tail(&ft->list, &fcoe_transports); + + ft->attached = true; + LIBFCOE_TRANSPORT_DBG("attaching transport %s\n", ft->name); + +out_attach: + mutex_unlock(&ft_mutex); + return rc; +} +EXPORT_SYMBOL(fcoe_transport_attach); + +/** + * fcoe_transport_detach - Detaches an FCoE transport + * @ft: The fcoe transport to be attached + * + * Returns : 0 for success + */ +int fcoe_transport_detach(struct fcoe_transport *ft) +{ + int rc = 0; + struct fcoe_netdev_mapping *nm = NULL, *tmp; + + mutex_lock(&ft_mutex); + if (!ft->attached) { + LIBFCOE_TRANSPORT_DBG("transport %s already detached\n", + ft->name); + rc = -ENODEV; + goto out_attach; + } + + /* remove netdev mapping for this transport as it is going away */ + mutex_lock(&fn_mutex); + list_for_each_entry_safe(nm, tmp, &fcoe_netdevs, list) { + if (nm->ft == ft) { + LIBFCOE_TRANSPORT_DBG("transport %s going away, " + "remove its netdev mapping for %s\n", + ft->name, nm->netdev->name); + list_del(&nm->list); + kfree(nm); + } + } + mutex_unlock(&fn_mutex); + + list_del(&ft->list); + ft->attached = false; + LIBFCOE_TRANSPORT_DBG("detaching transport %s\n", ft->name); + +out_attach: + mutex_unlock(&ft_mutex); + return rc; + +} +EXPORT_SYMBOL(fcoe_transport_detach); + +static int fcoe_transport_show(char *buffer, const struct kernel_param *kp) +{ + int i, j; + struct fcoe_transport *ft = NULL; + + i = j = sprintf(buffer, "Attached FCoE transports:"); + mutex_lock(&ft_mutex); + list_for_each_entry(ft, &fcoe_transports, list) { + if (i >= PAGE_SIZE - IFNAMSIZ) + break; + i += snprintf(&buffer[i], IFNAMSIZ, "%s ", ft->name); + } + mutex_unlock(&ft_mutex); + if (i == j) + i += snprintf(&buffer[i], IFNAMSIZ, "none"); + return i; +} + +static int __init fcoe_transport_init(void) +{ + register_netdevice_notifier(&libfcoe_notifier); + return 0; +} + +static int fcoe_transport_exit(void) +{ + struct fcoe_transport *ft; + + unregister_netdevice_notifier(&libfcoe_notifier); + mutex_lock(&ft_mutex); + list_for_each_entry(ft, &fcoe_transports, list) + printk(KERN_ERR "FCoE transport %s is still attached!\n", + ft->name); + mutex_unlock(&ft_mutex); + return 0; +} + + +static int fcoe_add_netdev_mapping(struct net_device *netdev, + struct fcoe_transport *ft) +{ + struct fcoe_netdev_mapping *nm; + + nm = kmalloc(sizeof(*nm), GFP_KERNEL); + if (!nm) { + printk(KERN_ERR "Unable to allocate netdev_mapping"); + return -ENOMEM; + } + + nm->netdev = netdev; + nm->ft = ft; + + mutex_lock(&fn_mutex); + list_add(&nm->list, &fcoe_netdevs); + mutex_unlock(&fn_mutex); + return 0; +} + + +static void fcoe_del_netdev_mapping(struct net_device *netdev) +{ + struct fcoe_netdev_mapping *nm = NULL, *tmp; + + mutex_lock(&fn_mutex); + list_for_each_entry_safe(nm, tmp, &fcoe_netdevs, list) { + if (nm->netdev == netdev) { + list_del(&nm->list); + kfree(nm); + mutex_unlock(&fn_mutex); + return; + } + } + mutex_unlock(&fn_mutex); +} + + +/** + * fcoe_netdev_map_lookup - find the fcoe transport that matches the netdev on which + * it was created + * @netdev: The net device that the FCoE interface is on + * + * Returns : ptr to the fcoe transport that supports this netdev or NULL + * if not found. + * + * The ft_mutex should be held when this is called + */ +static struct fcoe_transport *fcoe_netdev_map_lookup(struct net_device *netdev) +{ + struct fcoe_transport *ft = NULL; + struct fcoe_netdev_mapping *nm; + + mutex_lock(&fn_mutex); + list_for_each_entry(nm, &fcoe_netdevs, list) { + if (netdev == nm->netdev) { + ft = nm->ft; + mutex_unlock(&fn_mutex); + return ft; + } + } + + mutex_unlock(&fn_mutex); + return NULL; +} + +/** + * fcoe_if_to_netdev() - Parse a name buffer to get a net device + * @buffer: The name of the net device + * + * Returns: NULL or a ptr to net_device + */ +static struct net_device *fcoe_if_to_netdev(const char *buffer) +{ + char *cp; + char ifname[IFNAMSIZ + 2]; + + 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; +} + +/** + * libfcoe_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. + * + * Returns: 0 for success + */ +static int libfcoe_device_notification(struct notifier_block *notifier, + ulong event, void *ptr) +{ + struct net_device *netdev = netdev_notifier_info_to_dev(ptr); + + switch (event) { + case NETDEV_UNREGISTER: + LIBFCOE_TRANSPORT_DBG("NETDEV_UNREGISTER %s\n", + netdev->name); + fcoe_del_netdev_mapping(netdev); + break; + } + return NOTIFY_OK; +} + +ssize_t fcoe_ctlr_create_store(struct bus_type *bus, + const char *buf, size_t count) +{ + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + int rc = 0; + int err; + + mutex_lock(&ft_mutex); + + netdev = fcoe_if_to_netdev(buf); + if (!netdev) { + LIBFCOE_TRANSPORT_DBG("Invalid device %s.\n", buf); + rc = -ENODEV; + goto out_nodev; + } + + ft = fcoe_netdev_map_lookup(netdev); + if (ft) { + LIBFCOE_TRANSPORT_DBG("transport %s already has existing " + "FCoE instance on %s.\n", + ft->name, netdev->name); + rc = -EEXIST; + goto out_putdev; + } + + ft = fcoe_transport_lookup(netdev); + if (!ft) { + LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n", + netdev->name); + rc = -ENODEV; + goto out_putdev; + } + + /* pass to transport create */ + err = ft->alloc ? ft->alloc(netdev) : -ENODEV; + if (err) { + fcoe_del_netdev_mapping(netdev); + rc = -ENOMEM; + goto out_putdev; + } + + err = fcoe_add_netdev_mapping(netdev, ft); + if (err) { + LIBFCOE_TRANSPORT_DBG("failed to add new netdev mapping " + "for FCoE transport %s for %s.\n", + ft->name, netdev->name); + rc = -ENODEV; + goto out_putdev; + } + + LIBFCOE_TRANSPORT_DBG("transport %s succeeded to create fcoe on %s.\n", + ft->name, netdev->name); + +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + if (rc) + return rc; + return count; +} + +ssize_t fcoe_ctlr_destroy_store(struct bus_type *bus, + const char *buf, size_t count) +{ + int rc = -ENODEV; + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + + mutex_lock(&ft_mutex); + + netdev = fcoe_if_to_netdev(buf); + if (!netdev) { + LIBFCOE_TRANSPORT_DBG("invalid device %s.\n", buf); + goto out_nodev; + } + + ft = fcoe_netdev_map_lookup(netdev); + if (!ft) { + LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n", + netdev->name); + goto out_putdev; + } + + /* pass to transport destroy */ + rc = ft->destroy(netdev); + if (rc) + goto out_putdev; + + fcoe_del_netdev_mapping(netdev); + LIBFCOE_TRANSPORT_DBG("transport %s %s to destroy fcoe on %s.\n", + ft->name, (rc) ? "failed" : "succeeded", + netdev->name); + rc = count; /* required for successful return */ +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + return rc; +} + +/** + * fcoe_transport_create() - Create a fcoe interface + * @buffer: The name of the Ethernet interface to create on + * @kp: The associated kernel param + * + * Called from sysfs. This holds the ft_mutex while calling the + * registered fcoe transport's create function. + * + * Returns: 0 for success + */ +static int fcoe_transport_create(const char *buffer, + const struct kernel_param *kp) +{ + int rc = -ENODEV; + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + enum fip_mode fip_mode = (enum fip_mode)kp->arg; + + mutex_lock(&ft_mutex); + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) { + LIBFCOE_TRANSPORT_DBG("Invalid device %s.\n", buffer); + goto out_nodev; + } + + ft = fcoe_netdev_map_lookup(netdev); + if (ft) { + LIBFCOE_TRANSPORT_DBG("transport %s already has existing " + "FCoE instance on %s.\n", + ft->name, netdev->name); + rc = -EEXIST; + goto out_putdev; + } + + ft = fcoe_transport_lookup(netdev); + if (!ft) { + LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n", + netdev->name); + goto out_putdev; + } + + rc = fcoe_add_netdev_mapping(netdev, ft); + if (rc) { + LIBFCOE_TRANSPORT_DBG("failed to add new netdev mapping " + "for FCoE transport %s for %s.\n", + ft->name, netdev->name); + goto out_putdev; + } + + /* pass to transport create */ + rc = ft->create ? ft->create(netdev, fip_mode) : -ENODEV; + if (rc) + fcoe_del_netdev_mapping(netdev); + + LIBFCOE_TRANSPORT_DBG("transport %s %s to create fcoe on %s.\n", + ft->name, (rc) ? "failed" : "succeeded", + netdev->name); + +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + return rc; +} + +/** + * fcoe_transport_destroy() - Destroy a FCoE interface + * @buffer: The name of the Ethernet interface to be destroyed + * @kp: The associated kernel parameter + * + * Called from sysfs. This holds the ft_mutex while calling the + * registered fcoe transport's destroy function. + * + * Returns: 0 for success + */ +static int fcoe_transport_destroy(const char *buffer, + const struct kernel_param *kp) +{ + int rc = -ENODEV; + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + + mutex_lock(&ft_mutex); + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) { + LIBFCOE_TRANSPORT_DBG("invalid device %s.\n", buffer); + goto out_nodev; + } + + ft = fcoe_netdev_map_lookup(netdev); + if (!ft) { + LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n", + netdev->name); + goto out_putdev; + } + + /* pass to transport destroy */ + rc = ft->destroy ? ft->destroy(netdev) : -ENODEV; + fcoe_del_netdev_mapping(netdev); + LIBFCOE_TRANSPORT_DBG("transport %s %s to destroy fcoe on %s.\n", + ft->name, (rc) ? "failed" : "succeeded", + netdev->name); + +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + return rc; +} + +/** + * fcoe_transport_disable() - Disables a FCoE interface + * @buffer: The name of the Ethernet interface to be disabled + * @kp: The associated kernel parameter + * + * Called from sysfs. + * + * Returns: 0 for success + */ +static int fcoe_transport_disable(const char *buffer, + const struct kernel_param *kp) +{ + int rc = -ENODEV; + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + + mutex_lock(&ft_mutex); + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) + goto out_nodev; + + ft = fcoe_netdev_map_lookup(netdev); + if (!ft) + goto out_putdev; + + rc = ft->disable ? ft->disable(netdev) : -ENODEV; + +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + return rc; +} + +/** + * fcoe_transport_enable() - Enables a FCoE interface + * @buffer: The name of the Ethernet interface to be enabled + * @kp: The associated kernel parameter + * + * Called from sysfs. + * + * Returns: 0 for success + */ +static int fcoe_transport_enable(const char *buffer, + const struct kernel_param *kp) +{ + int rc = -ENODEV; + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + + mutex_lock(&ft_mutex); + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) + goto out_nodev; + + ft = fcoe_netdev_map_lookup(netdev); + if (!ft) + goto out_putdev; + + rc = ft->enable ? ft->enable(netdev) : -ENODEV; + +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + return rc; +} + +/** + * libfcoe_init() - Initialization routine for libfcoe.ko + */ +static int __init libfcoe_init(void) +{ + int rc = 0; + + rc = fcoe_transport_init(); + if (rc) + return rc; + + rc = fcoe_sysfs_setup(); + if (rc) + fcoe_transport_exit(); + + return rc; +} +module_init(libfcoe_init); + +/** + * libfcoe_exit() - Tear down libfcoe.ko + */ +static void __exit libfcoe_exit(void) +{ + fcoe_sysfs_teardown(); + fcoe_transport_exit(); +} +module_exit(libfcoe_exit); diff --git a/drivers/scsi/fcoe/libfcoe.h b/drivers/scsi/fcoe/libfcoe.h new file mode 100644 index 000000000..b8bdfab51 --- /dev/null +++ b/drivers/scsi/fcoe/libfcoe.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _FCOE_LIBFCOE_H_ +#define _FCOE_LIBFCOE_H_ + +extern unsigned int libfcoe_debug_logging; +#define LIBFCOE_LOGGING 0x01 /* General logging, not categorized */ +#define LIBFCOE_FIP_LOGGING 0x02 /* FIP logging */ +#define LIBFCOE_TRANSPORT_LOGGING 0x04 /* FCoE transport logging */ +#define LIBFCOE_SYSFS_LOGGING 0x08 /* fcoe_sysfs logging */ + +#define LIBFCOE_CHECK_LOGGING(LEVEL, CMD) \ +do { \ + if (unlikely(libfcoe_debug_logging & LEVEL)) \ + do { \ + CMD; \ + } while (0); \ +} while (0) + +#define LIBFCOE_DBG(fmt, args...) \ + LIBFCOE_CHECK_LOGGING(LIBFCOE_LOGGING, \ + pr_info("libfcoe: " fmt, ##args);) + +#define LIBFCOE_FIP_DBG(fip, fmt, args...) \ + LIBFCOE_CHECK_LOGGING(LIBFCOE_FIP_LOGGING, \ + pr_info("host%d: fip: " fmt, \ + (fip)->lp->host->host_no, ##args);) + +#define LIBFCOE_TRANSPORT_DBG(fmt, args...) \ + LIBFCOE_CHECK_LOGGING(LIBFCOE_TRANSPORT_LOGGING, \ + pr_info("%s: " fmt, __func__, ##args);) + +#define LIBFCOE_SYSFS_DBG(cdev, fmt, args...) \ + LIBFCOE_CHECK_LOGGING(LIBFCOE_SYSFS_LOGGING, \ + pr_info("ctlr_%d: " fmt, cdev->id, ##args);) + +#endif /* _FCOE_LIBFCOE_H_ */ |