Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 85b4aa49 authored by Robert Love's avatar Robert Love Committed by James Bottomley
Browse files

[SCSI] fcoe: Fibre Channel over Ethernet



Encapsulation protocol for running Fibre Channel over Ethernet interfaces.
Creates virtual Fibre Channel host adapters using libfc.

This layer is the LLD to the scsi-ml. It allocates the Scsi_Host, utilizes
libfc for Fibre Channel protocol processing and interacts with netdev to
send/receive Ethernet packets.

Signed-off-by: default avatarRobert Love <robert.w.love@intel.com>
Signed-off-by: default avatarJames Bottomley <James.Bottomley@HansenPartnership.com>
parent 42e9a92f
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -609,6 +609,13 @@ config LIBFC
	---help---
	  Fibre Channel library module

config FCOE
	tristate "FCoE module"
	depends on SCSI
	select LIBFC
	---help---
	  Fibre Channel over Ethernet module

config SCSI_DMX3191D
	tristate "DMX3191D SCSI support"
	depends on PCI && SCSI
+1 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ obj-$(CONFIG_SCSI_SRP_ATTRS) += scsi_transport_srp.o
obj-$(CONFIG_SCSI_DH)		+= device_handler/

obj-$(CONFIG_LIBFC)		+= libfc/
obj-$(CONFIG_FCOE)		+= fcoe/
obj-$(CONFIG_ISCSI_TCP) 	+= libiscsi.o	libiscsi_tcp.o iscsi_tcp.o
obj-$(CONFIG_INFINIBAND_ISER) 	+= libiscsi.o
obj-$(CONFIG_SCSI_A4000T)	+= 53c700.o	a4000t.o
+8 −0
Original line number Diff line number Diff line
# $Id: Makefile

obj-$(CONFIG_FCOE) += fcoe.o

fcoe-y := \
	libfcoe.o \
	fcoe_sw.o \
	fc_transport_fcoe.o
+446 −0
Original line number Diff line number Diff line
/*
 * Copyright(c) 2007 - 2008 Intel Corporation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Maintained at www.Open-FCoE.org
 */

#include <linux/pci.h>
#include <scsi/libfcoe.h>
#include <scsi/fc_transport_fcoe.h>

/* internal fcoe transport */
struct fcoe_transport_internal {
	struct fcoe_transport *t;
	struct net_device *netdev;
	struct list_head list;
};

/* fcoe transports list and its lock */
static LIST_HEAD(fcoe_transports);
static DEFINE_MUTEX(fcoe_transports_lock);

/**
 * fcoe_transport_default - returns ptr to the default transport fcoe_sw
 **/
struct fcoe_transport *fcoe_transport_default(void)
{
	return &fcoe_sw_transport;
}

/**
 * fcoe_transport_to_pcidev - get the pci dev from a netdev
 * @netdev: the netdev that pci dev will be retrived from
 *
 * Returns: NULL or the corrsponding pci_dev
 **/
struct pci_dev *fcoe_transport_pcidev(const struct net_device *netdev)
{
	if (!netdev->dev.parent)
		return NULL;
	return to_pci_dev(netdev->dev.parent);
}

/**
 * fcoe_transport_device_lookup - find out netdev is managed by the
 * transport
 * assign a transport to a device
 * @netdev: the netdev the transport to be attached to
 *
 * This will look for existing offload driver, if not found, it falls back to
 * the default sw hba (fcoe_sw) as its fcoe transport.
 *
 * Returns: 0 for success
 **/
static struct fcoe_transport_internal *fcoe_transport_device_lookup(
	struct fcoe_transport *t, struct net_device *netdev)
{
	struct fcoe_transport_internal *ti;

	/* assign the transpor to this device */
	mutex_lock(&t->devlock);
	list_for_each_entry(ti, &t->devlist, list) {
		if (ti->netdev == netdev) {
			mutex_unlock(&t->devlock);
			return ti;
		}
	}
	mutex_unlock(&t->devlock);
	return NULL;
}
/**
 * fcoe_transport_device_add - assign a transport to a device
 * @netdev: the netdev the transport to be attached to
 *
 * This will look for existing offload driver, if not found, it falls back to
 * the default sw hba (fcoe_sw) as its fcoe transport.
 *
 * Returns: 0 for success
 **/
static int fcoe_transport_device_add(struct fcoe_transport *t,
				     struct net_device *netdev)
{
	struct fcoe_transport_internal *ti;

	ti = fcoe_transport_device_lookup(t, netdev);
	if (ti) {
		printk(KERN_DEBUG "fcoe_transport_device_add:"
		       "device %s is already added to transport %s\n",
		       netdev->name, t->name);
		return -EEXIST;
	}
	/* allocate an internal struct to host the netdev and the list */
	ti = kzalloc(sizeof(*ti), GFP_KERNEL);
	if (!ti)
		return -ENOMEM;

	ti->t = t;
	ti->netdev = netdev;
	INIT_LIST_HEAD(&ti->list);
	dev_hold(ti->netdev);

	mutex_lock(&t->devlock);
	list_add(&ti->list, &t->devlist);
	mutex_unlock(&t->devlock);

	printk(KERN_DEBUG "fcoe_transport_device_add:"
		       "device %s added to transport %s\n",
		       netdev->name, t->name);

	return 0;
}

/**
 * fcoe_transport_device_remove - remove a device from its transport
 * @netdev: the netdev the transport to be attached to
 *
 * this removes the device from the transport so the given transport will
 * not manage this device any more
 *
 * Returns: 0 for success
 **/
static int fcoe_transport_device_remove(struct fcoe_transport *t,
					struct net_device *netdev)
{
	struct fcoe_transport_internal *ti;

	ti = fcoe_transport_device_lookup(t, netdev);
	if (!ti) {
		printk(KERN_DEBUG "fcoe_transport_device_remove:"
		       "device %s is not managed by transport %s\n",
		       netdev->name, t->name);
		return -ENODEV;
	}
	mutex_lock(&t->devlock);
	list_del(&ti->list);
	mutex_unlock(&t->devlock);
	printk(KERN_DEBUG "fcoe_transport_device_remove:"
	       "device %s removed from transport %s\n",
	       netdev->name, t->name);
	dev_put(ti->netdev);
	kfree(ti);
	return 0;
}

/**
 * fcoe_transport_device_remove_all - remove all from transport devlist
 *
 * this removes the device from the transport so the given transport will
 * not manage this device any more
 *
 * Returns: 0 for success
 **/
static void fcoe_transport_device_remove_all(struct fcoe_transport *t)
{
	struct fcoe_transport_internal *ti, *tmp;

	mutex_lock(&t->devlock);
	list_for_each_entry_safe(ti, tmp, &t->devlist, list) {
		list_del(&ti->list);
		kfree(ti);
	}
	mutex_unlock(&t->devlock);
}

/**
 * fcoe_transport_match - use the bus device match function to match the hw
 * @t: the fcoe transport
 * @netdev:
 *
 * This function is used to check if the givne transport wants to manage the
 * input netdev. if the transports implements the match function, it will be
 * called, o.w. we just compare the pci vendor and device id.
 *
 * Returns: true for match up
 **/
static bool fcoe_transport_match(struct fcoe_transport *t,
				struct net_device *netdev)
{
	/* match transport by vendor and device id */
	struct pci_dev *pci;

	pci = fcoe_transport_pcidev(netdev);

	if (pci) {
		printk(KERN_DEBUG "fcoe_transport_match:"
		       "%s:%x:%x -- %s:%x:%x\n",
		       t->name, t->vendor, t->device,
		       netdev->name, pci->vendor, pci->device);

		/* if transport supports match */
		if (t->match)
			return t->match(netdev);

		/* else just compare the vendor and device id: pci only */
		return (t->vendor == pci->vendor) && (t->device == pci->device);
	}
	return false;
}

/**
 * fcoe_transport_lookup - check if the transport is already registered
 * @t: the transport to be looked up
 *
 * This compares the parent device (pci) vendor and device id
 *
 * Returns: NULL if not found
 *
 * TODO - return default sw transport if no other transport is found
 **/
static struct fcoe_transport *fcoe_transport_lookup(
	struct net_device *netdev)
{
	struct fcoe_transport *t;

	mutex_lock(&fcoe_transports_lock);
	list_for_each_entry(t, &fcoe_transports, list) {
		if (fcoe_transport_match(t, netdev)) {
			mutex_unlock(&fcoe_transports_lock);
			return t;
		}
	}
	mutex_unlock(&fcoe_transports_lock);

	printk(KERN_DEBUG "fcoe_transport_lookup:"
	       "use default transport for %s\n", netdev->name);
	return fcoe_transport_default();
}

/**
 * fcoe_transport_register - adds a fcoe transport to the fcoe transports list
 * @t: ptr to the fcoe transport to be added
 *
 * Returns: 0 for success
 **/
int fcoe_transport_register(struct fcoe_transport *t)
{
	struct fcoe_transport *tt;

	/* TODO - add fcoe_transport specific initialization here */
	mutex_lock(&fcoe_transports_lock);
	list_for_each_entry(tt, &fcoe_transports, list) {
		if (tt == t) {
			mutex_unlock(&fcoe_transports_lock);
			return -EEXIST;
		}
	}
	list_add_tail(&t->list, &fcoe_transports);
	mutex_unlock(&fcoe_transports_lock);

	mutex_init(&t->devlock);
	INIT_LIST_HEAD(&t->devlist);

	printk(KERN_DEBUG "fcoe_transport_register:%s\n", t->name);

	return 0;
}
EXPORT_SYMBOL_GPL(fcoe_transport_register);

/**
 * fcoe_transport_unregister - remove the tranport fro the fcoe transports list
 * @t: ptr to the fcoe transport to be removed
 *
 * Returns: 0 for success
 **/
int fcoe_transport_unregister(struct fcoe_transport *t)
{
	struct fcoe_transport *tt, *tmp;

	mutex_lock(&fcoe_transports_lock);
	list_for_each_entry_safe(tt, tmp, &fcoe_transports, list) {
		if (tt == t) {
			list_del(&t->list);
			mutex_unlock(&fcoe_transports_lock);
			fcoe_transport_device_remove_all(t);
			printk(KERN_DEBUG "fcoe_transport_unregister:%s\n",
			       t->name);
			return 0;
		}
	}
	mutex_unlock(&fcoe_transports_lock);
	return -ENODEV;
}
EXPORT_SYMBOL_GPL(fcoe_transport_unregister);

/*
 * fcoe_load_transport_driver - load an offload driver by alias name
 * @netdev: the target net device
 *
 * Requests for an offload driver module as the fcoe transport, if fails, it
 * falls back to use the SW HBA (fcoe_sw) as its transport
 *
 * TODO -
 * 	1. supports only PCI device
 * 	2. needs fix for VLAn and bonding
 * 	3. pure hw fcoe hba may not have netdev
 *
 * Returns: 0 for success
 **/
int fcoe_load_transport_driver(struct net_device *netdev)
{
	struct pci_dev *pci;
	struct device *dev = netdev->dev.parent;

	if (fcoe_transport_lookup(netdev)) {
		/* load default transport */
		printk(KERN_DEBUG "fcoe: already loaded transport for %s\n",
		       netdev->name);
		return -EEXIST;
	}

	pci = to_pci_dev(dev);
	if (dev->bus != &pci_bus_type) {
		printk(KERN_DEBUG "fcoe: support noly PCI device\n");
		return -ENODEV;
	}
	printk(KERN_DEBUG "fcoe: loading driver fcoe-pci-0x%04x-0x%04x\n",
	       pci->vendor, pci->device);

	return request_module("fcoe-pci-0x%04x-0x%04x",
			      pci->vendor, pci->device);

}
EXPORT_SYMBOL_GPL(fcoe_load_transport_driver);

/**
 * fcoe_transport_attach - load transport to fcoe
 * @netdev: the netdev the transport to be attached to
 *
 * This will look for existing offload driver, if not found, it falls back to
 * the default sw hba (fcoe_sw) as its fcoe transport.
 *
 * Returns: 0 for success
 **/
int fcoe_transport_attach(struct net_device *netdev)
{
	struct fcoe_transport *t;

	/* find the corresponding transport */
	t = fcoe_transport_lookup(netdev);
	if (!t) {
		printk(KERN_DEBUG "fcoe_transport_attach"
		       ":no transport for %s:use %s\n",
		       netdev->name, t->name);
		return -ENODEV;
	}
	/* add to the transport */
	if (fcoe_transport_device_add(t, netdev)) {
		printk(KERN_DEBUG "fcoe_transport_attach"
		       ":failed to add %s to tramsport %s\n",
		       netdev->name, t->name);
		return -EIO;
	}
	/* transport create function */
	if (t->create)
		t->create(netdev);

	printk(KERN_DEBUG "fcoe_transport_attach:transport %s for %s\n",
	       t->name, netdev->name);
	return 0;
}
EXPORT_SYMBOL_GPL(fcoe_transport_attach);

/**
 * fcoe_transport_release - unload transport from fcoe
 * @netdev: the net device on which fcoe is to be released
 *
 * Returns: 0 for success
 **/
int fcoe_transport_release(struct net_device *netdev)
{
	struct fcoe_transport *t;

	/* find the corresponding transport */
	t = fcoe_transport_lookup(netdev);
	if (!t) {
		printk(KERN_DEBUG "fcoe_transport_release:"
		       "no transport for %s:use %s\n",
		       netdev->name, t->name);
		return -ENODEV;
	}
	/* remove the device from the transport */
	if (fcoe_transport_device_remove(t, netdev)) {
		printk(KERN_DEBUG "fcoe_transport_release:"
		       "failed to add %s to tramsport %s\n",
		       netdev->name, t->name);
		return -EIO;
	}
	/* transport destroy function */
	if (t->destroy)
		t->destroy(netdev);

	printk(KERN_DEBUG "fcoe_transport_release:"
	       "device %s dettached from transport %s\n",
	       netdev->name, t->name);

	return 0;
}
EXPORT_SYMBOL_GPL(fcoe_transport_release);

/**
 * fcoe_transport_init - initializes fcoe transport layer
 *
 * This prepares for the fcoe transport layer
 *
 * Returns: none
 **/
int __init fcoe_transport_init(void)
{
	INIT_LIST_HEAD(&fcoe_transports);
	mutex_init(&fcoe_transports_lock);
	return 0;
}

/**
 * fcoe_transport_exit - cleans up the fcoe transport layer
 * This cleans up the fcoe transport layer. removing any transport on the list,
 * note that the transport destroy func is not called here.
 *
 * Returns: none
 **/
int __exit fcoe_transport_exit(void)
{
	struct fcoe_transport *t, *tmp;

	mutex_lock(&fcoe_transports_lock);
	list_for_each_entry_safe(t, tmp, &fcoe_transports, list) {
		list_del(&t->list);
		mutex_unlock(&fcoe_transports_lock);
		fcoe_transport_device_remove_all(t);
		mutex_lock(&fcoe_transports_lock);
	}
	mutex_unlock(&fcoe_transports_lock);
	return 0;
}
+494 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading