[PATCH 04/10] IOQNET: Add a test harness infrastructure to IOQNET

Gregory Haskins
2007-08-16T23:14:18Z

We can add a IOQNET loop-back device and register it with the PVBUS to test
many aspects of the system (IOQ, PVBUS, and IOQNET itself).

Signed-off-by: Gregory Haskins <ghaskins-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org>
---

drivers/net/Kconfig | 10 +
drivers/net/ioqnet/Makefile | 3
drivers/net/ioqnet/loopback.c | 502 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 515 insertions(+), 0 deletions(-)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 7ee7454..426947d 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2957,6 +2957,16 @@ config IOQNET_DEBUG
depends on IOQNET
default n

+config IOQNET_LOOPBACK
+ tristate "IOQNET loopback device test harness"
+ depends on IOQNET
+ default n
+ ---help---
+ This will install a special PVBUS device that implements two IOQNET
+ devices. The devices are, of course, linked to one another forming a
+ loopback mechanism. This allows many subsystems to be tested: IOQ,
+ PVBUS, and IOQNET itself. If unsure, say N.
+
endif #NETDEVICES

config NETPOLL
diff --git a/drivers/net/ioqnet/Makefile b/drivers/net/ioqnet/Makefile
index d7020ee..7d2d156 100644
--- a/drivers/net/ioqnet/Makefile
+++ b/drivers/net/ioqnet/Makefile
@@ -4,8 +4,11 @@

ioqnet-objs = driver.o
obj-$(CONFIG_IOQNET) += ioqnet.o
+ioqnet-loopback-objs = loopback.o
+obj-$(CONFIG_IOQNET_LOOPBACK) += ioqnet-loopback.o

ifeq ($(CONFIG_IOQNET_DEBUG),y)
EXTRA_CFLAGS += -DIOQNET_DEBUG
endif
+
diff --git a/drivers/net/ioqnet/loopback.c b/drivers/net/ioqnet/loopback.c
new file mode 100644
index 0000000..0e36b43
--- /dev/null
+++ b/drivers/net/ioqnet/loopback.c
@@ -0,0 +1,502 @@
+/*
+ * ioqnet test harness
+ *
+ * Copyright (C) 2007 Novell, Gregory Haskins <ghaskins-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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.
+ */
+
+#include <linux/module.h>
+#include <linux/pvbus.h>
+#include <linux/ioq.h>
+#include <linux/kthread.h>
+#include <linux/ioqnet.h>
+#include <linux/interrupt.h>
+
+MODULE_AUTHOR("Gregory Haskins");
+MODULE_LICENSE("GPL");
+
+#ifndef ETH_ALEN
+#define ETH_ALEN 6
+#endif
+
+#undef PDEBUG /* undef it, just in case */
+#ifdef IOQNET_DEBUG
+# define PDEBUG(fmt, args...) printk( KERN_DEBUG "ioqnet: " fmt, ## args)
+#else
+# define PDEBUG(fmt, args...) /* not debugging: nothing */
+#endif
+
+/*
+ * ---------------------------------------------------------------------
+ * First we must create an IOQ implementation to use while under test
+ * since these operations will all be local to the same host
+ * ---------------------------------------------------------------------
+ */
+
+struct ioqnet_lb_ioq {
+ struct ioq ioq;
+ struct ioqnet_lb_ioq *peer;
+ struct tasklet_struct task;
+};
+
+struct ioqnet_lb_ioqmgr {
+ struct ioq_mgr mgr;
+
+ /*
+ * Since this is just a test harness, we know ahead of time that
+ * we aren't going to need more than a handful of IOQs. So to keep
+ * lookups simple we will simply create a static array of them
+ */
+ struct ioqnet_lb_ioq ioqs[8];
+ int pos;
+};
+
+static struct ioqnet_lb_ioqmgr lb_ioqmgr;
+
+struct ioqnet_lb_ioq* to_ioq(struct ioq *ioq)
+{
+ return container_of(ioq, struct ioqnet_lb_ioq, ioq);
+}
+
+struct ioqnet_lb_ioqmgr* to_mgr(struct ioq_mgr *mgr)
+{
+ return container_of(mgr, struct ioqnet_lb_ioqmgr, mgr);
+}
+
+/*
+ * ------------------
+ * ioq implementation
+ * ------------------
+ */
+static void ioqnet_lb_ioq_wake(unsigned long data)
+{
+ struct ioqnet_lb_ioq *_ioq = (struct ioqnet_lb_ioq*)data;
+
+ if (_ioq->peer)
+ ioq_wakeup(&_ioq->peer->ioq);
+}
+
+static int ioqnet_lb_ioq_signal(struct ioq *ioq)
+{
+ struct ioqnet_lb_ioq *_ioq = to_ioq(ioq);
+
+ if (_ioq->peer)
+ tasklet_schedule(&_ioq->task);
+
+ return 0;
+}
+
+static void ioqnet_lb_ioq_destroy(struct ioq *ioq)
+{
+ struct ioqnet_lb_ioq *_ioq = to_ioq(ioq);
+
+ if (_ioq->peer) {
+ _ioq->peer->peer = NULL;
+ _ioq->peer = NULL;
+ }
+
+ if (_ioq->ioq.locale == ioq_locality_north) {
+ kfree(_ioq->ioq.ring);
+ kfree(_ioq->ioq.head_desc);
+ } else
+ kfree(_ioq);
+}
+
+/*
+ * ------------------
+ * ioqmgr implementation
+ * ------------------
+ */
+static int ioqnet_lb_ioq_create(struct ioq_mgr *t, struct ioq **ioq,
+ size_t ringsize, int flags)
+{
+ struct ioqnet_lb_ioqmgr *mgr = to_mgr(t);
+ struct ioqnet_lb_ioq *_ioq = NULL;
+ struct ioq_ring_head *head_desc = NULL;
+ void *ring = NULL;
+ int ret = -ENOMEM;
+ size_t ringlen;
+ ioq_id_t id;
+
+ ringlen = sizeof(struct ioq_ring_desc) * ringsize;
+
+ BUG_ON(mgr->pos >= (sizeof(mgr->ioqs)/sizeof(mgr->ioqs[0])));
+
+ id = (ioq_id_t)mgr->pos++;
+
+ _ioq = &mgr->ioqs[id];
+ if (!_ioq)
+ goto error;
+
+ head_desc = kzalloc(sizeof(*head_desc), GFP_KERNEL);
+ if (!head_desc)
+ goto error;
+
+ ring = kzalloc(ringlen, GFP_KERNEL);
+ if (!ring)
+ goto error;
+
+ head_desc->magic = IOQ_RING_MAGIC;
+ head_desc->ver = IOQ_RING_VER;
+ head_desc->id = id;
+ head_desc->count = ringsize;
+ head_desc->ptr = (u64)ring;
+
+ ioq_init(&_ioq->ioq);
+
+ _ioq->ioq.signal = ioqnet_lb_ioq_signal;
+ _ioq->ioq.destroy = ioqnet_lb_ioq_destroy;
+
+ _ioq->ioq.id = head_desc->id;
+ _ioq->ioq.locale = ioq_locality_north;
+ _ioq->ioq.mgr = t;
+ _ioq->ioq.head_desc = head_desc;
+ _ioq->ioq.ring = ring;
+
+ tasklet_init(&_ioq->task, ioqnet_lb_ioq_wake, (unsigned long)_ioq);
+
+ *ioq = &_ioq->ioq;
+
+ return 0;
+
+ error:
+ if (head_desc)
+ kfree(head_desc);
+ if (ring)
+ kfree(ring);
+
+ return ret;
+}
+
+static int ioqnet_lb_ioq_connect(struct ioq_mgr *t, ioq_id_t id,
+ struct ioq **ioq, int flags)
+{
+ struct ioqnet_lb_ioqmgr *mgr = to_mgr(t);
+ struct ioqnet_lb_ioq *peer_ioq = &mgr->ioqs[id];
+ struct ioqnet_lb_ioq *_ioq;
+
+ if (peer_ioq->peer)
+ return -EEXIST;
+
+ _ioq = kzalloc(sizeof(*_ioq), GFP_KERNEL);
+ if (!_ioq)
+ return -ENOMEM;
+
+ ioq_init(&_ioq->ioq);
+
+ _ioq->ioq.signal = ioqnet_lb_ioq_signal;
+ _ioq->ioq.destroy = ioqnet_lb_ioq_destroy;
+
+ _ioq->ioq.id = id;
+ _ioq->ioq.locale = ioq_locality_south;
+ _ioq->ioq.mgr = t;
+ _ioq->ioq.head_desc = peer_ioq->ioq.head_desc;
+ _ioq->ioq.ring = peer_ioq->ioq.ring;
+
+ _ioq->peer = peer_ioq;
+ peer_ioq->peer = _ioq;
+ tasklet_init(&_ioq->task, ioqnet_lb_ioq_wake, (unsigned long)_ioq);
+
+ *ioq = &_ioq->ioq;
+
+ return 0;
+}
+
+static void ioqnet_lb_ioqmgr_init(void)
+{
+ struct ioqnet_lb_ioqmgr *mgr = &lb_ioqmgr;
+
+ memset(mgr, 0, sizeof(mgr));
+
+ mgr->mgr.create = ioqnet_lb_ioq_create;
+ mgr->mgr.connect = ioqnet_lb_ioq_connect;
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * Next we create the loopback device in terms of our ioqnet_lb_ioq
+ * subsystem
+ * ---------------------------------------------------------------------
+ */
+
+struct ioqnet_lb_device {
+ int idx; /* 0 or 1 */
+ struct ioq *rxq;
+ struct ioq *txq;
+ char mac[ETH_ALEN];
+ struct task_struct *task;
+ struct ioqnet_lb_device *peer;
+
+ struct pvbus_device dev;
+};
+
+struct ioqnet_lb_device* to_dev(struct pvbus_device *dev)
+{
+ return container_of(dev, struct ioqnet_lb_device, dev);
+}
+
+static void ioqnet_lb_xmit(struct ioqnet_lb_device *dev,
+ struct ioqnet_tx_ptr *ptr,
+ size_t count)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct ioq_iterator iter;
+ int ret;
+ int i;
+ char *dest;
+
+ add_wait_queue(&dev->txq->wq, &wait);
+
+ /* We want to iterate on the head of the in-use index for reading */
+ ret = ioq_iter_init(dev->txq, &iter, ioq_idxtype_inuse,
+ IOQ_ITER_AUTOUPDATE);
+ BUG_ON(ret < 0);
+
+ ret = ioq_iter_seek(&iter, ioq_seek_head, 0, 0);
+ BUG_ON(ret < 0);
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+
+ while (!iter.desc->valid || !iter.desc->sown)
+ schedule();
+
+ set_current_state(TASK_RUNNING);
+
+ dest = __va(iter.desc->ptr);
+
+ for (i = 0; i < count; ++i) {
+ struct ioqnet_tx_ptr *p = &ptr[i];
+ void *d = __va(p->data);
+
+ memcpy(dest, d, p->len);
+ dest += p->len;
+ }
+
+ mb();
+ iter.desc->sown = 0;
+ mb();
+
+ /* Advance the in-use head */
+ ret = ioq_iter_push(&iter, 0);
+ BUG_ON(ret < 0);
+}
+
+/*
+ * This is the daemon thread for each device that gets created once the guest
+ * side connects to us (via the pvbus_device->call(IOQNET_CONNECT) operation).
+ * We want to wait on packets to arrive on the rxq, and then send them to our
+ * peer's txq.
+ */
+static int ioqnet_lb_thread(void *data)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct ioqnet_lb_device *dev = (struct ioqnet_lb_device*)data;
+ struct ioq_iterator iter;
+ int ret;
+
+ add_wait_queue(&dev->rxq->wq, &wait);
+
+ /* We want to iterate on the tail of the in-use index for reading */
+ ret = ioq_iter_init(dev->rxq, &iter, ioq_idxtype_inuse,
+ IOQ_ITER_AUTOUPDATE);
+ BUG_ON(ret < 0);
+
+ ret = ioq_iter_seek(&iter, ioq_seek_tail, 0, 0);
+ BUG_ON(ret < 0);
+
+ while (1) {
+ struct ioq_ring_desc *desc = iter.desc;
+ struct ioqnet_tx_ptr *ptr;
+
+ PDEBUG("%d: Waiting...\n", dev->idx);
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+
+ while (!desc->sown)
+ schedule();
+
+ set_current_state(TASK_RUNNING);
+
+ PDEBUG("%d: Got a packet\n", dev->idx);
+
+ ptr = __va(desc->ptr);
+
+ /*
+ * If the peer is connected, we transmit it to their
+ * queue...otherwise we just drop it on the floor
+ */
+ if (dev->peer->txq)
+ ioqnet_lb_xmit(dev->peer, ptr, desc->alen);
+
+ mb();
+ desc->sown = 0;
+ mb();
+
+ /* Advance the in-use tail */
+ ret = ioq_iter_pop(&iter, 0);
+ BUG_ON(ret < 0);
+ }
+
+ return 0;
+}
+
+static int ioqnet_lb_dev_createqueue(struct pvbus_device *dev,
+ struct ioq **ioq,
+ size_t ringsize, int flags)
+{
+ struct ioq_mgr *ioqmgr = &lb_ioqmgr.mgr;
+
+ return ioqmgr->create(ioqmgr, ioq, ringsize, flags);
+}
+
+static int ioqnet_lb_queue_connect(ioq_id_t id, struct ioq **ioq)
+{
+ int ret;
+ struct ioq_mgr *ioqmgr = &lb_ioqmgr.mgr;
+
+ ret = ioqmgr->connect(ioqmgr, id, ioq, 0);
+ if (ret < 0)
+ return ret;
+
+ ioq_start(*ioq, 0);
+
+ return 0;
+}
+
+static int ioqnet_lb_dev_connect(struct ioqnet_lb_device *dev,
+ void *data, size_t len)
+{
+ struct ioqnet_connect *cnct = (struct ioqnet_connect*)data;
+ int ret;
+
+ /* We connect the north's rxq to our txq */
+ ret = ioqnet_lb_queue_connect(cnct->rxq, &dev->txq);
+ if (ret < 0)
+ return ret;
+
+ /* And vice-versa */
+ ret = ioqnet_lb_queue_connect(cnct->txq, &dev->rxq);
+ if (ret < 0)
+ return ret;
+
+ dev->task = kthread_create(ioqnet_lb_thread, dev,
+ "ioqnet-lb/%d", dev->idx);
+ wake_up_process(dev->task);
+
+ return 0;
+}
+
+static int ioqnet_lb_dev_query_mac(struct ioqnet_lb_device *dev,
+ void *data, size_t len)
+{
+ if (len != ETH_ALEN)
+ return -EINVAL;
+
+ memcpy(data, dev->mac, ETH_ALEN);
+
+ return 0;
+}
+
+/*
+ * This function is invoked whenever a guest calls pvbus_ops->call() against
+ * our instance ID
+ */
+static int ioqnet_lb_dev_call(struct pvbus_device *dev, u32 func, void *data,
+ size_t len, int flags)
+{
+ struct ioqnet_lb_device *_dev = to_dev(dev);
+ int ret = -EINVAL;
+
+ switch (func) {
+ case IOQNET_CONNECT:
+ ret = ioqnet_lb_dev_connect(_dev, data, len);
+ break;
+ case IOQNET_QUERY_MAC:
+ ret = ioqnet_lb_dev_query_mac(_dev, data, len);
+ break;
+ }
+
+ return ret;
+}
+
+static int ioqnet_lb_dev_init(struct ioqnet_lb_device *dev,
+ int idx,
+ struct ioqnet_lb_device *peer)
+{
+ char mac[] = { 0x00, 0x30, 0xcc, 0x00, 0x00, idx };
+
+ memset(dev, 0, sizeof(*dev));
+ dev->idx = idx;
+ dev->peer = peer;
+ memcpy(dev->mac, mac, ETH_ALEN);
+
+ dev->dev.name = IOQNET_NAME;
+ dev->dev.id = idx;
+ dev->dev.createqueue = ioqnet_lb_dev_createqueue;
+ dev->dev.call = ioqnet_lb_dev_call;
+ sprintf(dev->dev.dev.bus_id, "%d", idx);
+
+ return 0;
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * Finally we create the top-level object that binds it all together
+ * ---------------------------------------------------------------------
+ */
+
+
+struct ioqnet_lb {
+ struct ioqnet_lb_device devs[2];
+};
+
+static struct ioqnet_lb ioqnet_lb;
+
+__init int ioqnet_lb_init_module(void)
+{
+ int ret;
+ int i;
+
+ ioqnet_lb_ioqmgr_init();
+
+ /* First initialize both devices */
+ for (i = 0; i < 2; i++) {
+ ret = ioqnet_lb_dev_init(&ioqnet_lb.devs[i],
+ i,
+ &ioqnet_lb.devs[!i]);
+ BUG_ON(ret < 0);
+ }
+
+ /* Then register them together */
+ for (i = 0; i < 2; i++) {
+ ret = pvbus_device_register(&ioqnet_lb.devs[i].dev);
+ BUG_ON(ret < 0);
+ }
+
+ return 0;
+}
+
+__exit void ioqnet_lb_cleanup(void)
+{
+ int i;
+
+ for (i = 0; i < 2; i++)
+ pvbus_device_unregister(&ioqnet_lb.devs[i].dev);
+
+}
+
+
+module_init(ioqnet_lb_init_module);
+module_exit(ioqnet_lb_cleanup);