ASR_BASE
Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/package/kernel/mfp/files/fp_device.c b/package/kernel/mfp/files/fp_device.c
new file mode 100644
index 0000000..f7d4ad8
--- /dev/null
+++ b/package/kernel/mfp/files/fp_device.c
@@ -0,0 +1,691 @@
+/*
+ * Fastpath Devices
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU FP_ERR( Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) "mfp" " device:%s:%d: " fmt, __func__, __LINE__
+
+#include <net/ipv6.h>
+#include <linux/inet.h>
+#include "fp_common.h"
+#include "fp_device.h"
+#include "fp_core.h"
+#include "fp_ndisc.h"
+
+static struct fastpath_module *fp_device;
+
+#define STATS_TITLE_FMT \
+ "%-13.13s: total Slow Fast\n"
+#define STATS_DATA_FMT \
+ " %-10.10s: %10llu %10llu %10lu\n"
+
+static unsigned long long inline stats_diff(unsigned long long slow, unsigned long fast)
+{
+ return (slow < fast) ? 0 : slow - fast;
+}
+
+static int
+add_stats_to_buff(char *buf, struct fp_net_device *fdev, ssize_t max_size)
+{
+ struct rtnl_link_stats64 temp;
+ const struct rtnl_link_stats64 *stats;
+ struct fp_net_device_stats *stats_fast;
+ static const char *title_fmt = STATS_TITLE_FMT;
+ static const char *data_fmt = STATS_DATA_FMT;
+ int len;
+
+ stats = dev_get_stats(fdev->dev, &temp);
+
+ stats_fast = &fdev->stats;
+
+ len = scnprintf(buf, max_size, title_fmt, fdev->dev->name);
+
+ len += scnprintf(buf + len, max_size - len, data_fmt, "queue_stopped",
+ 0llu, 0llu, stats_fast->queue_stopped);
+ len += scnprintf(buf + len, max_size - len, data_fmt, "rx_packets",
+ stats->rx_packets, stats_diff(stats->rx_packets, stats_fast->rx_packets) ,stats_fast->rx_packets);
+ len += scnprintf(buf + len, max_size - len, data_fmt, "rx_bytes",
+ stats->rx_bytes, stats_diff(stats->rx_bytes, stats_fast->rx_bytes), stats_fast->rx_bytes);
+ len += scnprintf(buf + len, max_size - len, data_fmt, "rx_errors",
+ stats->rx_errors, stats_diff(stats->rx_errors, stats_fast->rx_errors), stats_fast->rx_errors);
+ len += scnprintf(buf + len, max_size - len, data_fmt, "rx_dropped",
+ stats->rx_dropped, stats_diff(stats->rx_dropped, stats_fast->rx_dropped), stats_fast->rx_dropped);
+ len += scnprintf(buf + len, max_size - len, data_fmt, "tx_packets",
+ stats->tx_packets, stats_diff(stats->tx_packets, stats_fast->tx_packets), stats_fast->tx_packets);
+ len += scnprintf(buf + len, max_size - len, data_fmt, "tx_bytes",
+ stats->tx_bytes, stats_diff(stats->tx_bytes, stats_fast->tx_bytes), stats_fast->tx_bytes);
+ len += scnprintf(buf + len, max_size - len, data_fmt, "tx_errors",
+ stats->tx_errors, stats_diff(stats->tx_errors, stats_fast->tx_errors), stats_fast->tx_errors);
+ len += scnprintf(buf + len, max_size - len, data_fmt, "tx_dropped",
+ stats->tx_dropped, stats_diff(stats->tx_dropped, stats_fast->tx_dropped), stats_fast->tx_dropped);
+ return len;
+}
+
+static int
+add_status_to_buff(char *buf, struct fp_net_device *fdev, ssize_t max_size)
+{
+ return scnprintf(buf, max_size, "%16s%8s%11s%9d%9d%9s\n",
+ fdev->dev->name,
+ netif_running(fdev->dev) ? "Up" : "Down",
+ fdev->forward ? "enabled" : "disabled",
+ atomic_read(&fdev->refcnt),
+ netdev_refcnt_read(fdev->dev),
+ fdev->br ? fdev->br->name : "NA");
+}
+
+static inline bool ip6addr_is_empty(struct in6_addr *addr)
+{
+ return !addr->in6_u.u6_addr32[0] &&
+ !addr->in6_u.u6_addr32[1] &&
+ !addr->in6_u.u6_addr32[2] &&
+ !addr->in6_u.u6_addr32[3];
+}
+
+static ssize_t fdev_forward_show(struct fp_net_device *fdev, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%s\n", fdev->forward ? "Enabled" : "Disabled");
+}
+
+static ssize_t fdev_forward_store(struct fp_net_device *fdev,
+ const char *buf, size_t count)
+{
+ unsigned int forward;
+
+ if (sscanf(buf, "%u", &forward) != 1)
+ return -EINVAL;
+
+ fdev->forward = (bool)forward;
+
+ return count;
+}
+
+/**
+ * show statistics
+ */
+static ssize_t fdev_stats_show(struct fp_net_device *fdev, char *buf)
+{
+ struct net_device_stats *stats_slow = &fdev->dev->stats;
+ struct fp_net_device_stats *stats_fast = &fdev->stats;
+
+ if (fdev->dev->netdev_ops && fdev->dev->netdev_ops->ndo_get_stats)
+ stats_slow = fdev->dev->netdev_ops->ndo_get_stats(fdev->dev);
+ stats_fast = &fdev->stats;
+
+ return add_stats_to_buff(buf, fdev, PAGE_SIZE - 1);;
+}
+
+/**
+ * clear statistics
+ * 0 - clear fast stats only
+ * 1 - clear slow & fast stats
+ */
+static ssize_t fdev_stats_store(struct fp_net_device *fdev,
+ const char *buf, size_t count)
+{
+ struct net_device_stats *stats_slow = &fdev->dev->stats;
+ struct fp_net_device_stats *stats_fast = &fdev->stats;
+ unsigned int op;
+
+ if (sscanf(buf, "%u", &op) != 1 || op > 1)
+ return -EINVAL;
+
+ if (fdev->dev->netdev_ops && fdev->dev->netdev_ops->ndo_get_stats)
+ stats_slow = fdev->dev->netdev_ops->ndo_get_stats(fdev->dev);
+ stats_fast = &fdev->stats;
+
+ memset(stats_fast,0,sizeof(struct fp_net_device_stats));
+ if (op)
+ memset(stats_slow,0,sizeof(struct net_device_stats));
+
+ return count;
+}
+
+/**
+ * show status
+ */
+static ssize_t fdev_status_show(struct fp_net_device *fdev, char *buf)
+{
+ int len;
+
+ len = scnprintf(buf, PAGE_SIZE, " device state forward refcnt dev_ref bridge\n");
+ return len + add_status_to_buff(buf + len, fdev, PAGE_SIZE - len -1);
+}
+
+static ssize_t fpdev_prefixlen_store(struct fp_net_device *fpdev,
+ const char *buf, size_t count)
+{
+ int pref;
+ sscanf(buf, "%d\n", &pref);
+
+ fpdev->prefixlen = pref;
+
+ return count;
+}
+
+static ssize_t fpdev_prefixlen_show(struct fp_net_device *fpdev, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%d\n", fpdev->prefixlen);
+}
+
+static ssize_t fpdev_ll6addr_store(struct fp_net_device *fpdev,
+ const char *buf, size_t count)
+{
+ in6_pton(buf, -1, (u8 *)&fpdev->ll6addr.s6_addr, -1, NULL);
+
+ if (ip6addr_is_empty(&fpdev->ll6addr))
+ fpdev_clear_ll6(fpdev);
+ else
+ fpdev_set_ll6(fpdev);
+
+ memset(&fpdev->gb6addr, 0, sizeof(struct in6_addr));
+ fpdev->prefixlen = 0;
+ fpdev->mtu = 0;
+ fpdev_clear_gb6(fpdev);
+ fpdev_clear_mtu(fpdev);
+
+ return count;
+}
+
+static ssize_t fpdev_ll6addr_show(struct fp_net_device *fpdev, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%pI6c\n", &fpdev->ll6addr);
+}
+
+static ssize_t fpdev_gb6addr_store(struct fp_net_device *fpdev,
+ const char *buf, size_t count)
+{
+ in6_pton(buf, -1, (u8 *)&fpdev->gb6addr.s6_addr, -1, NULL);
+
+ fpdev_set_gb6(fpdev);
+ return count;
+}
+
+static ssize_t fpdev_gb6addr_show(struct fp_net_device *fpdev, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%pI6c\n", &fpdev->gb6addr);
+}
+
+static ssize_t fpdev_mtu_store(struct fp_net_device *fpdev,
+ const char *buf, size_t count)
+{
+ u32 mtu;
+ sscanf(buf, "%d\n", &mtu);
+
+ fpdev->mtu = mtu;
+
+ return count;
+}
+
+
+static ssize_t fpdev_mtu_show(struct fp_net_device *fpdev, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%d\n", fpdev->mtu);
+}
+
+struct fp_dev_attr {
+ struct attribute attr;
+ ssize_t (*show)(struct fp_net_device *, char *);
+ ssize_t (*store)(struct fp_net_device *, const char *, size_t count);
+};
+
+#define FPDEV_ATTR(_name, _mode, _show, _store) \
+ struct fp_dev_attr fp_dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
+
+#define to_fpdev(fpdev) container_of(fpdev, struct fp_net_device, kobj)
+#define to_attr(a) container_of(a, struct fp_dev_attr, attr)
+
+static FPDEV_ATTR(forward, S_IRUGO|S_IWUSR, fdev_forward_show, fdev_forward_store);
+static FPDEV_ATTR(statistics, S_IRUGO|S_IWUSR, fdev_stats_show, fdev_stats_store);
+static FPDEV_ATTR(status, S_IRUGO, fdev_status_show, NULL);
+static FPDEV_ATTR(ll6addr, S_IRUGO|S_IWUSR, fpdev_ll6addr_show,
+ fpdev_ll6addr_store);
+static FPDEV_ATTR(gb6addr, S_IRUGO|S_IWUSR, fpdev_gb6addr_show,
+ fpdev_gb6addr_store);
+static FPDEV_ATTR(prefixlen, S_IRUGO|S_IWUSR, fpdev_prefixlen_show,
+ fpdev_prefixlen_store);
+static FPDEV_ATTR(mtu, S_IRUGO|S_IWUSR, fpdev_mtu_show,
+ fpdev_mtu_store);
+
+static struct attribute *fpdev_default_attrs[] = {
+ &fp_dev_attr_forward.attr,
+ &fp_dev_attr_statistics.attr,
+ &fp_dev_attr_status.attr,
+ &fp_dev_attr_ll6addr.attr,
+ &fp_dev_attr_gb6addr.attr,
+ &fp_dev_attr_prefixlen.attr,
+ &fp_dev_attr_mtu.attr,
+ NULL
+};
+
+static ssize_t fpdev_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ struct fp_net_device *fdev = to_fpdev(kobj);
+ struct fp_dev_attr *fattr = to_attr(attr);
+
+ if (!fdev || !fattr || !fattr->show)
+ return -EINVAL;
+
+ return fattr->show(fdev, buf);
+}
+
+static ssize_t fpdev_store(struct kobject *kobj, struct attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fp_net_device *fdev = to_fpdev(kobj);
+ struct fp_dev_attr *fattr = to_attr(attr);
+
+ if (!fdev || !fattr || !fattr->store)
+ return -EINVAL;
+
+ return fattr->store(fdev, buf, count);
+}
+
+static const struct sysfs_ops fpdev_sysfs_ops = {
+ .show = fpdev_show,
+ .store = fpdev_store,
+};
+
+void destroy_fpdev(struct work_struct *w)
+{
+ struct fp_dev_work *work;
+ struct fp_net_device *fpdev;
+ struct fp_dev_list *fpdl;
+
+ work = container_of(w, struct fp_dev_work, work.work);
+ BUG_ON(!work);
+
+ fpdev = work->fpdev;
+ fpdl = work->fpdl;
+
+ pr_err("device (%s) destroyed\n", fpdev->dev->name);
+
+ rtnl_lock();
+ dev_put(fpdev->dev);
+ rtnl_unlock();
+
+ kfree(fpdev);
+ atomic_dec(&fpdl->dev_count);
+ wake_up(&fpdl->wq);
+
+ list_del(&work->list);
+ kfree(work);
+
+}
+
+void destroy_fpdev_rcu(struct rcu_head *rcu)
+{
+ struct fp_dev_work *work;
+ struct fp_net_device *fpdev =
+ container_of(rcu, struct fp_net_device, rcu);
+ struct fp_dev_list *fpdl = fp_device->priv;
+
+ work = kzalloc(sizeof(*work), GFP_ATOMIC);
+ if (!work)
+ return;
+
+ work->fpdev = fpdev;
+ work->fpdl = fpdl;
+
+ INIT_LIST_HEAD(&work->list);
+ INIT_DELAYED_WORK(&work->work, destroy_fpdev);
+ queue_delayed_work(fpdl->dev_put_wq, &work->work, 0);
+}
+
+static void release_fpdev(struct kobject *kobj)
+{
+ pr_debug("fpdev kobj released\n");
+}
+
+static struct kobj_type ktype_fpdev = {
+ .sysfs_ops = &fpdev_sysfs_ops,
+ .default_attrs = fpdev_default_attrs,
+ .release = release_fpdev,
+};
+
+static void fpdev_del_if_finish(struct work_struct *work)
+{
+ struct fp_net_device *fpdev;
+
+ fpdev = container_of(work, struct fp_net_device, free_work);
+
+ kobject_put(&fpdev->kobj);
+ fpdev_put(fpdev);
+}
+
+/*--------------------------------------------------------------*/
+/*- API -*/
+/*--------------------------------------------------------------*/
+
+/**
+ * delete the fastpath device associated with this net device
+ *
+ * @param dev net device
+ *
+ * @return 0 for success, -ENODEV if not found
+ */
+int fpdev_del_if(struct net_device *dev)
+{
+ struct fp_net_device *fpdev;
+ struct fp_dev_list *fpdl = fp_device->priv;
+
+ spin_lock_bh(&fpdl->list_lock);
+ rcu_read_lock_bh();
+ list_for_each_entry_rcu(fpdev, &fpdl->devices_list, list) {
+ if (fpdev->dev == dev && fpdev_hold(fpdev))
+ goto found;
+ }
+
+ fpdev = NULL;
+
+found:
+ rcu_read_unlock_bh();
+
+ if (!fpdev) {
+ pr_debug("device (%s) not found\n", dev->name);
+ spin_unlock_bh(&fpdl->list_lock);
+ return -ENODEV;
+ }
+
+ list_del_rcu(&fpdev->list);
+ spin_unlock_bh(&fpdl->list_lock);
+
+ fpdev_put(fpdev);
+ schedule_work(&fpdev->free_work);
+
+ fpdev_put(fpdev);
+
+ printk(KERN_DEBUG "device (%s) found and deleted\n", dev->name);
+ return 0;
+}
+
+/**
+ * create and add a fastpath device for a given interface
+ *
+ * @param dev net device
+ *
+ * @return 0 for success, error code otherwise
+ */
+int fpdev_add_if(struct net_device *dev)
+{
+ struct fp_net_device *fpdev;
+ struct fp_dev_list *fpdl;
+ int ret;
+
+ BUG_ON(!dev);
+ BUG_ON(!fp_device);
+
+ fpdl = fp_device->priv;
+
+ fpdev = kzalloc(sizeof(*fpdev), GFP_ATOMIC);
+ if (!fpdev) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ dev_hold(dev);
+
+ ret = kobject_init_and_add(&fpdev->kobj, &ktype_fpdev, &fp_device->kobj,
+ dev->name);
+ if (ret)
+ goto kobj_err;
+
+
+ fpdev->forward = true;
+ fpdev->dev = dev;
+ INIT_LIST_HEAD(&fpdev->list);
+ INIT_WORK(&fpdev->free_work, fpdev_del_if_finish);
+
+ /* extra reference for return */
+ atomic_set(&fpdev->refcnt, 2);
+ atomic_inc(&fpdl->dev_count);
+
+ spin_lock_bh(&fpdl->list_lock);
+ list_add_tail_rcu(&fpdev->list, &fpdl->devices_list);
+ spin_unlock_bh(&fpdl->list_lock);
+
+ kobject_uevent(&fpdev->kobj, KOBJ_ADD);
+
+ pr_debug("created fastpath device for %s\n", dev->name);
+
+ return 0;
+
+kobj_err:
+ kobject_put(&fpdev->kobj);
+ dev_put(dev);
+ kfree(fpdev);
+err:
+ pr_err("could not creat fastpath device for %s\n", dev->name);
+ return ret;
+}
+
+/**
+ * search for a fastpath device associated with a given net device.
+ * If found, the fastpath device's refcount is incremented.
+ * The user must call fpdev_put() when finished in order to release the device.
+ *
+ * @param dev net device
+ *
+ * @return pointer to the associated fastpath device (NULL if not found)
+ */
+struct fp_net_device *fpdev_get_if(struct net_device *dev)
+{
+ struct fp_net_device *fpdev;
+ struct fp_dev_list *fpdl = fp_device->priv;
+
+ rcu_read_lock_bh();
+ list_for_each_entry_rcu(fpdev, &fpdl->devices_list, list) {
+ if (fpdev->dev == dev && atomic_inc_not_zero(&fpdev->refcnt))
+ goto found;
+ }
+
+ fpdev = NULL;
+ printk(KERN_DEBUG "device (%s) not found\n", dev->name);
+
+found:
+ rcu_read_unlock_bh();
+ return fpdev;
+}
+
+struct fp_net_device *fpdev_get_ccinet(void)
+{
+ struct fp_net_device *fpdev;
+ struct fp_dev_list *fpdl = fp_device->priv;
+
+ rcu_read_lock_bh();
+ list_for_each_entry_rcu(fpdev, &fpdl->devices_list, list) {
+ if (fpdev_is_gb6_set(fpdev) && fpdev_is_ll6_set(fpdev) &&
+ (!strncasecmp(fpdev->dev->name, "ccinet", 6)) &&
+ fpdev_is_mtu_set(fpdev) && atomic_inc_not_zero(&fpdev->refcnt))
+ goto found;
+ }
+
+ fpdev = NULL;
+
+found:
+ rcu_read_unlock_bh();
+ return fpdev;
+}
+
+/**
+ * show statistics (all fastpath devices)
+ */
+static ssize_t stats_show(struct fastpath_module *m, char *buf)
+{
+ struct fp_net_device *itr;
+ int len, res;
+ struct fp_dev_list *fpdl = fp_device->priv;
+
+ len = sprintf(buf, "fastpath statistics\n");
+
+ spin_lock_bh(&fpdl->list_lock);
+ list_for_each_entry(itr, &fpdl->devices_list, list) {
+ if (!netif_running(itr->dev) || !fpdev_hold(itr))
+ continue;
+ res = add_stats_to_buff(buf + len, itr, PAGE_SIZE - len - 1);
+ fpdev_put(itr);
+ len += res;
+ if (res == 0) {
+ pr_info("Exceed PAGE_SIZE, result trancated\n");
+ len += sprintf(buf + len, "\n");
+ break;
+ }
+ }
+ spin_unlock_bh(&fpdl->list_lock);
+
+ return len;
+}
+
+/**
+ * clear statistics (all fastpath devices)
+ * 0 - clear fast stats only
+ * 1 - clear slow & fast stats
+ */
+static ssize_t stats_store(struct fastpath_module *m, const char *buf,
+ size_t count)
+{
+ struct fp_net_device *itr;
+ struct net_device_stats *stats_slow;
+ struct fp_net_device_stats *stats_fast;
+ unsigned int op;
+ struct fp_dev_list *fpdl = fp_device->priv;
+
+ if (sscanf(buf, "%u", &op) != 1 || op > 1)
+ return -EINVAL;
+
+ spin_lock_bh(&fpdl->list_lock);
+ list_for_each_entry(itr, &fpdl->devices_list, list) {
+ BUG_ON(!itr->dev);
+ if (!fpdev_hold(itr))
+ continue;
+ stats_slow = &itr->dev->stats;
+ stats_fast = &itr->stats;
+ if (itr->dev->netdev_ops && itr->dev->netdev_ops->ndo_get_stats)
+ stats_slow = itr->dev->netdev_ops->ndo_get_stats(itr->dev);
+
+ memset(stats_fast,0,sizeof(struct fp_net_device_stats));
+ if (op)
+ memset(stats_slow,0,sizeof(struct net_device_stats));
+
+ fpdev_put(itr);
+ }
+ spin_unlock_bh(&fpdl->list_lock);
+
+ return count;
+}
+
+/**
+ * show status (all fastpath devices)
+ */
+static ssize_t status_show(struct fastpath_module *m, char *buf)
+{
+ struct fp_net_device *itr;
+ struct fp_dev_list *fpdl = fp_device->priv;
+ int len = 0;
+
+ len = scnprintf(buf, PAGE_SIZE, " device state forward refcnt dev_ref bridge\n");
+
+ /* active devices */
+ rcu_read_lock_bh();
+ list_for_each_entry_rcu(itr, &fpdl->devices_list, list)
+ len += add_status_to_buff(buf + len, itr, PAGE_SIZE - len -1);
+ rcu_read_unlock_bh();
+
+ return len;
+}
+
+static void fp_device_release(struct kobject *kobj)
+{
+ struct fp_dev_list *fpdl = fp_device->priv;
+ BUG_ON(!list_empty(&fpdl->devices_list));
+ pr_debug("fp_device released\n");
+}
+
+static FP_ATTR(devices, S_IRUGO, status_show, NULL);
+static FP_ATTR(stats, S_IRUGO|S_IWUSR, stats_show, stats_store);
+
+static struct attribute *fp_device_attrs[] = {
+ &fp_attr_devices.attr,
+ &fp_attr_stats.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct kobj_type ktype_devices = {
+ .sysfs_ops = &fp_sysfs_ops,
+ .default_attrs = fp_device_attrs,
+ .release = fp_device_release,
+};
+
+static int fp_device_probe(struct fastpath_module *module)
+{
+ int ret;
+ struct fp_dev_list *fpdl;
+
+ snprintf(module->name, sizeof(module->name),"fp_device");
+
+ fpdl = kzalloc(sizeof(*fpdl), GFP_KERNEL);
+ if (!fpdl) {
+ pr_err("fp_dev_list alloc failed\n");
+ return -ENOMEM;
+ }
+
+ kobject_init(&module->kobj, &ktype_devices);
+ ret = kobject_add(&module->kobj, module->fastpath->kobj, "%s", module->name);
+ if (ret < 0) {
+ pr_err("kobject_add failed (%d)\n", ret);
+ goto kobj_err;
+ }
+
+ atomic_set(&fpdl->dev_count, 0);
+ INIT_LIST_HEAD(&fpdl->devices_list);
+ spin_lock_init(&fpdl->list_lock);
+ init_waitqueue_head(&fpdl->wq);
+
+ fpdl->dev_put_wq = create_singlethread_workqueue(module->name);
+ if (!fpdl->dev_put_wq) {
+ pr_err("create workqueue failed\n");
+ ret = -EBUSY;
+ goto kobj_err;
+ }
+
+ module->priv = fpdl;
+ fp_device = module;
+
+ kobject_uevent(&module->kobj, KOBJ_ADD);
+
+ pr_debug("fp_device probed\n");
+ return 0;
+
+kobj_err:
+ kobject_put(&module->kobj);
+ kfree(fpdl);
+ return ret;
+}
+
+static int fp_device_remove(struct fastpath_module *module)
+{
+ struct fp_dev_list *fpdl = fp_device->priv;
+
+ BUG_ON(!module);
+
+ flush_workqueue(fpdl->dev_put_wq);
+ wait_event(fpdl->wq, !atomic_read(&fpdl->dev_count));
+ destroy_workqueue(fpdl->dev_put_wq);
+
+ kobject_put(&module->kobj);
+ fp_device = NULL;
+
+ kfree(module->priv);
+ kfree(module);
+
+ pr_debug("fp_device removed\n");
+ return 0;
+}
+
+struct fastpath_module_ops fp_device_ops = {
+ .probe = fp_device_probe,
+ .remove = fp_device_remove,
+};