/* * 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 #include #include /* 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; }