[PATCH/RFC 8/9] Virtual network host switch support

Carsten Otte
2007-05-11T17:36:08Z

From: Christian Borntraeger <cborntra-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>

This is the host counterpart for the virtual network device driver. This driver
has an char device node where the hypervisor can attach. It also
has a kind of dumb switch that passes packets between guests. Last but not least
it contains a host network interface. Patches for attaching other host network
devices to the switch via raw sockets, extensions to qeth or netfilter are
currently tested but not ready yet. We did not use the linux bridging code to
allow non-root users to create virtual networks between guests.

Signed-off-by: Christian Borntraeger <cborntra-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Carsten Otte <cotte-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>

---
drivers/s390/guest/Makefile | 3
drivers/s390/guest/vnet_port_guest.c | 302 ++++++++++++
drivers/s390/guest/vnet_port_guest.h | 21
drivers/s390/guest/vnet_port_host.c | 418 +++++++++++++++++
drivers/s390/guest/vnet_port_host.h | 18
drivers/s390/guest/vnet_switch.c | 828 +++++++++++++++++++++++++++++++++++
drivers/s390/guest/vnet_switch.h | 119 +++++
drivers/s390/net/Kconfig | 12
8 files changed, 1721 insertions(+)

Index: linux-2.6.21/drivers/s390/guest/vnet_port_guest.c
===================================================================
--- /dev/null
+++ linux-2.6.21/drivers/s390/guest/vnet_port_guest.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2005 IBM Corporation
+ * Authors: Carsten Otte <cotte-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ * Christian Borntraeger <borntrae-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ *
+ */
+#include <linux/etherdevice.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+
+#include "vnet.h"
+#include "vnet_port_guest.h"
+#include "vnet_switch.h"
+
+static void COFIXME_add_irq(struct vnet_guest_port *zgp, int data)
+{
+ int oldval, newval;
+
+ do {
+ oldval = atomic_read(&zgp->pending_irqs);
+ newval = oldval | data;
+ } while (atomic_cmpxchg(&zgp->pending_irqs, oldval, newval) != oldval);
+}
+
+static int COFIXME_get_irq(struct vnet_guest_port *zgp)
+{
+ int oldval;
+
+ do {
+ oldval = atomic_read(&zgp->pending_irqs);
+ } while (atomic_cmpxchg(&zgp->pending_irqs, oldval, 0) != oldval);
+
+ return oldval;
+}
+
+static void
+vnet_guest_interrupt(struct vnet_port *port, int type)
+{
+ struct vnet_guest_port *priv;
+
+ priv = port->priv;
+
+ if (!priv->fasync) {
+ printk (KERN_WARNING "vnet: cannot send interrupt,"
+ "fd not async\n");
+ return;
+ }
+ switch (type) {
+ case VNET_IRQ_START_RX:
+ COFIXME_add_irq(priv, POLLIN);
+ kill_fasync(&priv->fasync, SIGIO, POLL_IN);
+ break;
+ case VNET_IRQ_START_TX:
+ COFIXME_add_irq(priv, POLLOUT);
+ kill_fasync(&priv->fasync, SIGIO, POLL_OUT);
+ break;
+ default:
+ BUG();
+ }
+}
+
+/* release all pinned user pages*/
+static void
+vnet_guest_release_pages(struct vnet_port *port)
+{
+ int i,j;
+
+ for (i=0; i<VNET_QUEUE_LEN; i++)
+ for (j=0; j<VNET_BUFFER_PAGES; j++) {
+ if (port->s2p_data[i][j]) {
+ page_cache_release(virt_to_page(port->s2p_data[i][j]));
+ port->s2p_data[i][j] = NULL;
+ }
+ if (port->p2s_data[i][j]) {
+ page_cache_release(virt_to_page(port->p2s_data[i][j]));
+ port->p2s_data[i][j] = NULL;
+ }
+ }
+ if (port->control) {
+ page_cache_release(virt_to_page(port->control));
+ port->control = NULL;
+ }
+}
+
+static int
+vnet_chr_open(struct inode *ino, struct file *filp)
+{
+ int minor;
+ struct vnet_port *port;
+ char name[BUS_ID_SIZE];
+
+ minor = iminor(filp->f_dentry->d_inode);
+ snprintf(name, BUS_ID_SIZE, "guest:%d", current->pid);
+ port = vnet_port_get(minor, name);
+ if (!port)
+ return -ENODEV;
+ port->priv = kzalloc(sizeof(struct vnet_guest_port), GFP_KERNEL);
+ if (!port->priv) {
+ vnet_port_put(port);
+ return -ENOMEM;
+ }
+ port->interrupt = vnet_guest_interrupt;
+ filp->private_data = port;
+ return nonseekable_open(ino, filp);
+}
+
+static int
+vnet_chr_release (struct inode *ino, struct file *filp)
+{
+ struct vnet_port *port;
+ port = (struct vnet_port *) filp->private_data;
+
+//FIXME: what about open close? We unregister non exisiting mac addresses
+// in vnet_port_detach!
+ vnet_port_detach(port);
+ vnet_guest_release_pages(port);
+ vnet_port_put(port);
+ return 0;
+}
+
+
+/* helper function which maps a user page into the kernel
+ * the memory must be free with page_cache_release */
+static void *user_to_kernel(char __user *user)
+{
+ struct page *temp_page;
+ int rc;
+
+ BUG_ON(((unsigned long) user) % PAGE_SIZE);
+ rc = fault_in_pages_writeable(user, PAGE_SIZE);
+ if (rc)
+ return NULL;
+ rc = get_user_pages(current, current->mm, (unsigned long) user,
+ 1, 1, 1, &temp_page, NULL);
+ if (rc != 1)
+ return NULL;
+ return page_address(temp_page);
+}
+
+/* this function pins the userspace buffers into memory*/
+static int
+vnet_guest_alloc_pages(struct vnet_port *port)
+{
+ int i,j;
+
+ down_read(&current->mm->mmap_sem);
+ for (i=0; i<VNET_QUEUE_LEN; i++)
+ for (j=0; j<VNET_BUFFER_PAGES; j++) {
+ port->s2p_data[i][j] = user_to_kernel(port->control->
+ s2pbufs[i].data + j*PAGE_SIZE);
+ if (!port->s2p_data[i][j])
+ goto cleanup;
+ port->p2s_data[i][j] = user_to_kernel(port->control->
+ p2sbufs[i].data + j*PAGE_SIZE);
+ if (!port->p2s_data[i][j])
+ goto cleanup;
+
+ }
+ up_read(&current->mm->mmap_sem);
+ return 0;
+cleanup:
+ up_read(&current->mm->mmap_sem);
+ vnet_guest_release_pages(port);
+ return -ENOMEM;
+}
+
+/* userspace control data structure stuff */
+static int
+vnet_register_control(struct vnet_port *port, unsigned long user_addr)
+{
+ u64 uaddr;
+ int rc;
+ struct page *control_page;
+
+ rc = copy_from_user(&uaddr, (void __user *) user_addr, sizeof(uaddr));
+ if (rc)
+ return -EFAULT;
+ if (uaddr % PAGE_SIZE)
+ return -EFAULT;
+ down_read(&current->mm->mmap_sem);
+ rc = get_user_pages(current, current->mm, (unsigned long)uaddr,
+ 1, 1, 1, &control_page, NULL);
+ up_read(&current->mm->mmap_sem);
+ if (rc!=1)
+ return -EFAULT;
+ port->control = (struct vnet_control *) page_address(control_page);
+ rc = vnet_guest_alloc_pages(port);
+ if (rc) {
+ printk("vnet: could not get buffers\n");
+ return rc;
+ }
+ random_ether_addr(port->mac);
+ memcpy(port->control->mac, port->mac,6);
+ vnet_port_attach(port);
+ return 0;
+}
+
+static int
+vnet_interrupt(struct vnet_port *port, int __user *u_type)
+{
+ int type, rc;
+
+ rc = copy_from_user (&type, u_type, sizeof(int));
+ if (rc)
+ return -EFAULT;
+ switch (type) {
+ case VNET_IRQ_START_RX:
+ vnet_port_rx(port);
+ break;
+ case VNET_IRQ_START_TX: /* noop with current drop packet approach*/
+ break;
+ default:
+ printk(KERN_ERR "vnet: Unknown interrupt type %d\n", type);
+ rc = -EINVAL;
+ }
+ return rc;
+}
+
+
+
+
+//this is a HACK. >>COFIXME<<
+unsigned int
+vnet_poll(struct file *filp, poll_table * wait)
+{
+ struct vnet_port *port;
+ struct vnet_guest_port *zgp;
+
+ port = filp->private_data;
+ zgp = port->priv;
+ return COFIXME_get_irq(zgp);
+}
+
+static int vnet_fill_info(struct vnet_port *zp, void __user *data)
+{
+ struct vnet_info info;
+
+ info.linktype = zp->zs->linktype;
+ info.maxmtu=32768; //FIXME
+ return copy_to_user(data, &info, sizeof(info));
+}
+long
+vnet_ioctl(struct file *filp, unsigned int no, unsigned long data)
+{
+ struct vnet_port *port =
+ (struct vnet_port *) filp->private_data;
+ int rc;
+
+ switch (no) {
+ case VNET_REGISTER_CTL:
+ rc = vnet_register_control(port, data);
+ break;
+ case VNET_INTERRUPT:
+ rc = vnet_interrupt(port, (int __user *) data);
+ break;
+ case VNET_INFO:
+ rc = vnet_fill_info(port, (void __user *) data);
+ break;
+ default:
+ rc = -ENOTTY;
+ }
+ return rc;
+}
+
+int vnet_fasync(int fd, struct file *filp, int on)
+{
+ struct vnet_port *port;
+ struct vnet_guest_port *zgp;
+ int rc;
+
+ port = filp->private_data;
+ zgp = port->priv;
+
+ if ((rc = fasync_helper(fd, filp, on, &zgp->fasync)) < 0)
+ return rc;
+
+ if (on)
+ rc = f_setown(filp, current->pid, 0);
+ return rc;
+}
+
+
+static struct file_operations vnet_char_fops = {
+ .owner = THIS_MODULE,
+ .open = vnet_chr_open,
+ .release = vnet_chr_release,
+ .unlocked_ioctl = vnet_ioctl,
+ .fasync = vnet_fasync,
+ .poll = vnet_poll,
+};
+
+
+
+void vnet_cdev_init(struct cdev *cdev)
+{
+ cdev_init(cdev, &vnet_char_fops);
+}
Index: linux-2.6.21/drivers/s390/guest/vnet_port_guest.h
===================================================================
--- /dev/null
+++ linux-2.6.21/drivers/s390/guest/vnet_port_guest.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2005 IBM Corporation
+ * Authors: Carsten Otte <cotte-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ * Christian Borntraeger <cborntra-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ *
+ */
+
+#ifndef __VNET_PORTS_GUEST_H
+#define __VNET_PORTS_GUEST_H
+
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <asm/atomic.h>
+
+struct vnet_guest_port {
+ struct fasync_struct *fasync;
+ atomic_t pending_irqs;
+};
+
+extern void vnet_cdev_init(struct cdev *cdev);
+#endif
Index: linux-2.6.21/drivers/s390/guest/vnet_port_host.c
===================================================================
--- /dev/null
+++ linux-2.6.21/drivers/s390/guest/vnet_port_host.c
@@ -0,0 +1,418 @@
+/*
+ * vnet zlswitch handling
+ *
+ * Copyright (C) 2005 IBM Corporation
+ * Authors: Carsten Otte <cotte-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ * Christian Borntraeger <borntrae-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ *
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/pagemap.h>
+#include <linux/spinlock.h>
+
+#include "vnet.h"
+#include "vnet_switch.h"
+#include "vnet_port_host.h"
+
+static void
+vnet_host_interrupt(struct vnet_port *zp, int type)
+{
+ struct vnet_host_port *zhp;
+
+ zhp = zp->priv;
+
+ BUG_ON(!zhp->netdev);
+
+ switch (type) {
+ case VNET_IRQ_START_RX:
+ netif_rx_schedule(zhp->netdev);
+ break;
+ case VNET_IRQ_START_TX:
+ netif_wake_queue(zhp->netdev);
+ break;
+ default:
+ BUG();
+ }
+ /* we are called via system call path. enforce softirq handling */
+ do_softirq();
+}
+
+static void
+vnet_host_free(struct vnet_port *zp)
+{
+ int i,j;
+
+ for (i=0; i<VNET_QUEUE_LEN; i++)
+ for (j=0; j<VNET_BUFFER_PAGES; j++) {
+ if (zp->s2p_data[i][j]) {
+ free_page((unsigned long) zp->s2p_data[i][j]);
+ zp->s2p_data[i][j] = NULL;
+ }
+ if (zp->p2s_data[i][j]) {
+ free_page((unsigned long) zp->p2s_data[i][j]);
+ zp->p2s_data[i][j] = NULL;
+ }
+ }
+ if (zp->control) {
+ kfree(zp->control);
+ zp->control = NULL;
+ }
+}
+
+static int
+vnet_port_hostsetup(struct vnet_port *zp)
+{
+ int i,j;
+
+ zp->control = kzalloc(sizeof(*zp->control), GFP_KERNEL);
+ if (!zp->control)
+ return -ENOMEM;
+ for (i=0; i<VNET_QUEUE_LEN; i++)
+ for (j=0; j<VNET_BUFFER_PAGES; j++) {
+ zp->s2p_data[i][j] = (void *) __get_free_pages(GFP_KERNEL,0);
+ if (!zp->s2p_data[i][j])
+ goto oom;
+ zp->p2s_data[i][j] = (void *) __get_free_pages(GFP_KERNEL,0);
+ if (!zp->p2s_data[i][j]) {
+ free_page((unsigned long) zp->s2p_data[i][j]);
+ goto oom;
+ }
+ }
+ zp->control->buffer_size = VNET_BUFFER_SIZE;
+ return 0;
+oom:
+ printk(KERN_WARNING "vnet: No memory for buffer space of host device\n");
+ vnet_host_free(zp);
+ return -ENOMEM;
+}
+
+/* host interface specific parts */
+
+
+static int
+vnet_net_open(struct net_device *dev)
+{
+ struct vnet_port *port;
+ struct vnet_control *control;
+
+ port = dev->priv;
+ control = port->control;
+ atomic_set(&control->s2pmit, 0);
+ netif_start_queue(dev);
+ return 0;
+}
+
+static int
+vnet_net_stop(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ return 0;
+}
+
+static void vnet_net_tx_timeout(struct net_device *dev)
+{
+ struct vnet_port *port = dev->priv;
+ struct vnet_control *control = port->control;
+
+ printk(KERN_ERR "problems in xmit for device %s\n Resetting...\n",
+ dev->name);
+ atomic_set(&control->p2smit, 0);
+ atomic_set(&control->s2pmit, 0);
+ vnet_port_rx(port);
+ netif_wake_queue(dev);
+}
+
+
+static int
+vnet_net_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct vnet_port *zhost;
+ struct vnet_host_port *zhp;
+ struct vnet_control *control;
+ struct xmit_buffer *buf;
+ int buffer_status;
+ int pkid;
+
+ zhost = dev->priv;
+ zhp = zhost->priv;
+ control = zhost->control;
+
+ if (!spin_trylock(&zhost->txlock))
+ return NETDEV_TX_LOCKED;
+ if (vnet_q_full(atomic_read(&control->p2smit))) {
+ netif_stop_queue(dev);
+ goto full;
+ }
+ pkid = __nextx(atomic_read(&control->p2smit));
+ buf = &control->p2sbufs[pkid];
+ buf->len = skb->len;
+ buf->proto = skb->protocol;
+ vnet_copy_buf_to_pages(zhost->p2s_data[pkid], skb->data, skb->len);
+ buffer_status = vnet_tx_packet(&control->p2smit);
+ spin_unlock(&zhost->txlock);
+ zhp->stats.tx_packets++;
+ zhp->stats.tx_bytes += skb->len;
+ dev_kfree_skb(skb);
+ dev->trans_start = jiffies;
+ if (buffer_status & QUEUE_WAS_EMPTY)
+ vnet_port_rx(zhost);
+ if (buffer_status & QUEUE_IS_FULL) {
+ netif_stop_queue(dev);
+ spin_lock(&zhost->txlock);
+ } else
+ return NETDEV_TX_OK;
+full:
+ /* we might have raced against the wakeup */
+ if (!vnet_q_full(atomic_read(&control->p2smit)))
+ netif_start_queue(dev);
+ spin_unlock(&zhost->txlock);
+ return NETDEV_TX_OK;
+}
+
+static int
+vnet_l3_poll(struct net_device *dev, int *budget)
+{
+ struct vnet_port *zp = dev->priv;
+ struct vnet_host_port *zhp = zp->priv;
+ struct vnet_control *control = zp->control;
+ struct xmit_buffer *buf;
+ struct sk_buff *skb;
+ int pkid, count, numpackets = min(64, min(dev->quota, *budget));
+ int buffer_status;
+
+ if (vnet_q_empty(atomic_read(&control->s2pmit))) {
+ count = 0;
+ goto empty;
+ }
+loop:
+ count = 0;
+ while(numpackets) {
+ pkid = __nextr(atomic_read(&control->s2pmit));
+ buf = &control->s2pbufs[pkid];
+ skb = dev_alloc_skb(buf->len + 2);
+ if (likely(skb)) {
+ skb_reserve(skb, 2);
+ vnet_copy_pages_to_buf(skb_put(skb, buf->len),
+ zp->s2p_data[pkid], buf->len);
+ skb->dev = dev;
+ skb->protocol = buf->proto;
+// skb->ip_summed = CHECKSUM_UNNECESSARY;
+ zhp->stats.rx_packets++;
+ zhp->stats.rx_bytes += buf->len;
+ netif_receive_skb(skb);
+ numpackets--;
+ (*budget)--;
+ dev->quota--;
+ count++;
+ } else
+ zhp->stats.rx_dropped++;
+ buffer_status = vnet_rx_packet(&control->s2pmit);
+ if (buffer_status & QUEUE_IS_EMPTY)
+ goto empty;
+ }
+ return 1; //please ask us again
+empty:
+ netif_rx_complete(dev);
+ /* we might have raced against a wakup*/
+ if (!vnet_q_empty(atomic_read(&control->s2pmit))) {
+ if (netif_rx_reschedule(dev, count))
+ goto loop;
+ }
+ return 0;
+}
+
+
+static int
+vnet_l2_poll(struct net_device *dev, int *budget)
+{
+ struct vnet_port *zp = dev->priv;
+ struct vnet_host_port *zhp = zp->priv;
+ struct vnet_control *control = zp->control;
+ struct xmit_buffer *buf;
+ struct sk_buff *skb;
+ int pkid, count, numpackets = min(64, min(dev->quota, *budget));
+ int buffer_status;
+
+ if (vnet_q_empty(atomic_read(&control->s2pmit))) {
+ count = 0;
+ goto empty;
+ }
+loop:
+ count = 0;
+ while(numpackets) {
+ pkid = __nextr(atomic_read(&control->s2pmit));
+ buf = &control->s2pbufs[pkid];
+ skb = dev_alloc_skb(buf->len + 2);
+ if (likely(skb)) {
+ skb_reserve(skb, 2);
+ vnet_copy_pages_to_buf(skb_put(skb, buf->len),
+ zp->s2p_data[pkid], buf->len);
+ skb->dev = dev;
+ skb->protocol = eth_type_trans(skb, dev);
+// skb->ip_summed = CHECKSUM_UNNECESSARY;
+ zhp->stats.rx_packets++;
+ zhp->stats.rx_bytes += buf->len;
+ netif_receive_skb(skb);
+ numpackets--;
+ (*budget)--;
+ dev->quota--;
+ count++;
+ } else
+ zhp->stats.rx_dropped++;
+ buffer_status = vnet_rx_packet(&control->s2pmit);
+ if (buffer_status & QUEUE_IS_EMPTY)
+ goto empty;
+ }
+ return 1; //please ask us again
+empty:
+ netif_rx_complete(dev);
+ /* we might have raced against a wakup*/
+ if (!vnet_q_empty(atomic_read(&control->s2pmit))) {
+ if (netif_rx_reschedule(dev, count))
+ goto loop;
+ }
+ return 0;
+}
+
+static struct net_device_stats *
+vnet_net_stats(struct net_device *dev)
+{
+ struct vnet_port *zp;
+ struct vnet_host_port *zhp;
+
+ zp = dev->priv;
+ zhp = zp->priv;
+ return &zhp->stats;
+}
+
+static int
+vnet_net_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if (new_mtu <= ETH_ZLEN)
+ return -ERANGE;
+ if (new_mtu > VNET_BUFFER_SIZE-ETH_HLEN)
+ return -ERANGE;
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+static void
+__vnet_common_init(struct net_device *dev)
+{
+ dev->open = vnet_net_open;
+ dev->stop = vnet_net_stop;
+ dev->hard_start_xmit = vnet_net_xmit;
+ dev->get_stats = vnet_net_stats;
+ dev->tx_timeout = vnet_net_tx_timeout;
+ dev->watchdog_timeo = VNET_TIMEOUT;
+ dev->change_mtu = vnet_net_change_mtu;
+ dev->weight = 64;
+ //dev->features |= NETIF_F_NO_CSUM | NETIF_F_LLTX;
+ dev->features |= NETIF_F_LLTX;
+}
+
+static void
+__vnet_layer3_init(struct net_device *dev)
+{
+ dev->mtu = ETH_DATA_LEN;
+ dev->tx_queue_len = 1000;
+ dev->flags = IFF_BROADCAST|IFF_MULTICAST|IFF_NOARP;
+ dev->type = ARPHRD_PPP;
+ dev->mtu = 1492;
+ dev->poll = vnet_l3_poll;
+ __vnet_common_init(dev);
+}
+
+static void
+__vnet_layer2_init(struct net_device *dev)
+{
+ ether_setup(dev);
+ random_ether_addr(dev->dev_addr);
+ dev->mtu = 1492;
+ dev->poll = vnet_l2_poll;
+ __vnet_common_init(dev);
+}
+
+static void
+vnet_host_destroy(struct vnet_port *zhost)
+{
+ struct vnet_host_port *zhp;
+ zhp = zhost->priv;
+
+ vnet_port_detach(zhost);
+ unregister_netdev(zhp->netdev);
+ free_netdev(zhp->netdev);
+ zhp->netdev = NULL;
+ vnet_host_free(zhost);
+ kfree(zhp);
+ vnet_port_put(zhost);
+}
+
+
+
+struct vnet_port *
+vnet_host_create(char *name)
+{
+ int rc;
+ struct vnet_port *port;
+ struct vnet_host_port *host;
+ char busname[BUS_ID_SIZE];
+ int minor;
+
+ snprintf(busname, BUS_ID_SIZE, "host:%s", name);
+
+ minor = vnet_minor_by_name(name);
+ if (minor < 0)
+ return NULL;
+ port = vnet_port_get(minor, busname);
+ if (!port)
+ goto out;
+ host = kzalloc(sizeof(struct vnet_host_port), GFP_KERNEL);
+ if (!host) {
+ kfree(port);
+ port = NULL;
+ goto out;
+ }
+ port->priv = host;
+ rc =vnet_port_hostsetup(port);
+ if (rc)
+ goto out_free_host;
+ rtnl_lock();
+ if (port->zs->linktype == 2)
+ host->netdev = alloc_netdev(0, name, __vnet_layer2_init);
+ else
+ host->netdev = alloc_netdev(0, name, __vnet_layer3_init);
+ if (!host->netdev)
+ goto out_unlock;
+ memcpy(port->mac, host->netdev->dev_addr, ETH_ALEN);
+
+ host->netdev->priv = port;
+ port->interrupt = vnet_host_interrupt;
+ port->destroy = vnet_host_destroy;
+
+ if (!register_netdevice(host->netdev)) {
+ /* good case */
+ rtnl_unlock();
+ return port;
+ }
+ host->netdev->priv = NULL;
+ free_netdev(host->netdev);
+ host->netdev = NULL;
+out_unlock:
+ rtnl_unlock();
+ vnet_host_free(port);
+out_free_host:
+ vnet_port_put(port);
+ port = NULL;
+out:
+ return port;
+}
Index: linux-2.6.21/drivers/s390/guest/vnet_port_host.h
===================================================================
--- /dev/null
+++ linux-2.6.21/drivers/s390/guest/vnet_port_host.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2005 IBM Corporation
+ * Christian Borntraeger <cborntra-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ *
+ */
+
+#ifndef __VNET_PORTS_HOST_H
+#define __VNET_PORTS_HOST_H
+
+#include <linux/netdevice.h>
+#include "vnet_switch.h"
+
+struct vnet_host_port {
+ struct net_device_stats stats;
+ struct net_device *netdev;
+};
+extern struct vnet_port * vnet_host_create(char *name);
+#endif
Index: linux-2.6.21/drivers/s390/guest/vnet_switch.c
===================================================================
--- /dev/null
+++ linux-2.6.21/drivers/s390/guest/vnet_switch.c
@@ -0,0 +1,828 @@
+/*
+ * vnet zlswitch handling
+ *
+ * Copyright (C) 2005 IBM Corporation
+ * Author: Carsten Otte <cotte-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ * Author: Christian Borntraeger <borntrae-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/fs.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/pagemap.h>
+#include <linux/spinlock.h>
+
+#include "vnet.h"
+#include "vnet_port_guest.h"
+#include "vnet_port_host.h"
+#include "vnet_switch.h"
+
+#define NUM_MINORS 1024
+
+/* devices housekeeping, creation & destruction */
+static LIST_HEAD(vnet_switches);
+static rwlock_t vnet_switches_lock = RW_LOCK_UNLOCKED;
+static struct class *zwitch_class;
+static int vnet_major;
+static struct device *root_dev;
+
+
+/* The following functions allow ports of the switch to know about
+ * the MAC addresses of other ports. This is necessary for special
+ * hardware like OSA express which silently drops incoming packets
+ * that not match known MAC addresses and do not support promiscous
+ * mode as well. We have to register all guest MAC addresses at OSA
+ * make packet receive working */
+
+/* Announces the own MAC address to all other ports
+ * this function is called if a new port is added */
+static void vnet_switch_add_mac(struct vnet_port *port)
+{
+ struct vnet_port *other_port;
+
+ read_lock(&port->zs->ports_lock);
+ list_for_each_entry(other_port, &port->zs->switch_ports, lh)
+ if ((other_port != port) && (other_port->set_mac))
+ other_port->set_mac(other_port,port->mac, 1);
+ read_unlock(&port->zs->ports_lock);
+}
+
+/* Removes the own MAC address from all other ports
+ * this function is called if a port is detached*/
+static void vnet_switch_del_mac(struct vnet_port *port)
+{
+ struct vnet_port *other_port;
+
+ read_lock(&port->zs->ports_lock);
+ list_for_each_entry(other_port, &port->zs->switch_ports, lh)
+ if (other_port->set_mac)
+ other_port->set_mac(other_port, port->mac, 0);
+ read_unlock(&port->zs->ports_lock);
+}
+
+/* Learn MACs from other ports on the same zwitch and forward
+ * the MAC addresses to the set_mac function of the port.*/
+static void __vnet_port_learn_macs(struct vnet_port *port)
+{
+ struct vnet_port *other_port;
+
+ if (!port->set_mac)
+ return;
+ list_for_each_entry(other_port, &port->zs->switch_ports, lh)
+ if (other_port != port)
+ port->set_mac(port, other_port->mac, 1);
+}
+
+/* Unlearn MACS from other ports on the same zwitch */
+static void __vnet_port_unlearn_macs(struct vnet_port *port)
+{
+ struct vnet_port *other_port;
+
+ if (!port->set_mac)
+ return;
+ list_for_each_entry(other_port, &port->zs->switch_ports, lh)
+ if (other_port != port)
+ port->set_mac(port, other_port->mac, 0);
+}
+
+
+static struct vnet_switch *__vnet_switch_by_minor(int minor)
+{
+ struct vnet_switch *zs;
+
+ list_for_each_entry(zs, &vnet_switches, lh) {
+ if (MINOR(zs->cdev.dev) == minor)
+ return zs;
+ }
+ return NULL;
+}
+
+static struct vnet_switch *__vnet_switch_by_name(char *name)
+{
+ struct vnet_switch *zs;
+
+ list_for_each_entry(zs, &vnet_switches, lh)
+ if (strncmp(zs->name, name, ZWITCH_NAME_SIZE) == 0)
+ return zs;
+ return NULL;
+}
+
+/* Returns a switch structure and increases the reference count. If no such
+ * switch exists a new one is created with reference count 1 */
+static struct vnet_switch *zwitch_get(int minor)
+{
+ struct vnet_switch *zs;
+
+ read_lock(&vnet_switches_lock);
+ zs = __vnet_switch_by_minor(minor);
+ if (!zs) {
+ read_unlock(&vnet_switches_lock);
+ return zs;
+ }
+ get_device(&zs->dev);
+ read_unlock(&vnet_switches_lock);
+ return zs;
+}
+
+/* reduces the reference count of the switch. */
+static void zwitch_put(struct vnet_switch * zs)
+{
+ put_device(&zs->dev);
+}
+
+/* looks into the packet and searches a matching MAC address
+ * return NULL if unknown or broadcast */
+static struct vnet_port *__vnet_find_l2(struct vnet_switch *zs, char *data)
+{
+ //FIXME: make this a hash lookup, more macs per device?
+ struct vnet_port *port;
+
+ if (is_multicast_ether_addr(data))
+ return NULL;
+ list_for_each_entry(port, &zs->switch_ports, lh) {
+ if (compare_ether_addr(port->mac, data)==0)
+ goto out;
+ }
+ port = NULL;
+ out:
+ return port;
+}
+
+/* searches the destination for IP only interfaces. Normally routing
+ * is the way to go, but guests should see the net transparently without
+ * a hop in between*/
+static struct vnet_port *__vnet_find_l3(struct vnet_switch *zs, char *data)
+{
+ return NULL;
+}
+
+static struct vnet_port * __vnet_find_destination(struct vnet_switch *zs,
+ char *data)
+{
+ switch (zs->linktype) {
+ case 2:
+ return __vnet_find_l2(zs, data);
+ case 3:
+ return __vnet_find_l3(zs, data);
+ default:
+ BUG();
+ }
+}
+
+/* copies len bytes of data from the memory specified by the list of
+ * pointers **from into the memory specified by the list of pointers **to
+ * with each pointer pointing to a page */
+static void
+vnet_switch_page_copy(void **to, void **from, int len)
+{
+ int remaining=len;
+ int pageid = 0;
+ int amount;
+
+ while(remaining) {
+ amount = min((int)PAGE_SIZE, remaining);
+ memcpy(to[pageid], from[pageid], amount);
+ pageid++;
+ remaining -= amount;
+ }
+}
+
+/* copies to data into a buffer of destination
+ * returns 0 if ok*/
+static int
+vnet_unicast(struct vnet_port *destination, void **from_data, int len, int proto)
+{
+ int pkid;
+ int buffer_status;
+ void **to_data;
+ struct vnet_control *control;
+
+ control = destination->control;
+ spin_lock_bh(&destination->rxlock);
+ if (vnet_q_full(atomic_read(&control->s2pmit))) {
+ destination->rx_dropped++;
+ spin_unlock_bh(&destination->rxlock);
+ return -ENOBUFS;
+ }
+ pkid = __nextx(atomic_read(&control->s2pmit));
+ to_data = destination->s2p_data[pkid];
+ vnet_switch_page_copy(to_data, from_data, len);
+ control->s2pbufs[pkid].len = len;
+ control->s2pbufs[pkid].proto = proto;
+ buffer_status = vnet_tx_packet(&control->s2pmit);
+ spin_unlock_bh(&destination->rxlock);
+ if (buffer_status & QUEUE_WAS_EMPTY)
+ destination->interrupt(destination, VNET_IRQ_START_RX);
+ destination->rx_bytes += len;
+ destination->rx_packets++;
+ return 0;
+}
+
+/* send packets to all ports and emulate broadcasts via unicasts*/
+static int vnet_allcast(struct vnet_port *from_port, void **fromdata,
+ int len, int proto)
+{
+ struct vnet_port *destination;
+ int failure = 0;
+
+ list_for_each_entry(destination, &from_port->zs->switch_ports, lh)
+ if (destination != from_port)
+ failure |= vnet_unicast(destination, fromdata,
+ len, proto);
+ return failure;
+}
+
+/* takes an incoming packet and forwards it to the right port
+ * if a failure occurs, increase the tx_dropped count of the sender*/
+static void vnet_switch_packet(struct vnet_port *from_port,
+ void **from_data, int len, int proto)
+{
+ struct vnet_port *destination;
+ int failure;
+
+ read_lock(&from_port->zs->ports_lock);
+ destination = __vnet_find_destination(from_port->zs, from_data[0]);
+ /* we dont want to loop. FIXME: document when this can happen*/
+ if (destination == from_port) {
+ read_unlock(&from_port->zs->ports_lock);
+ return;
+ }
+ if (destination)
+ failure = vnet_unicast(destination, from_data, len, proto);
+ else
+ failure = vnet_allcast(from_port, from_data, len, proto);
+ read_unlock(&from_port->zs->ports_lock);
+ if (failure)
+ from_port->tx_dropped++;
+ else {
+ from_port->tx_packets++;
+ from_port->tx_bytes += len;
+ }
+}
+
+static void vnet_port_release(struct device *dev)
+{
+ struct vnet_port *port;
+
+ port = container_of(dev, struct vnet_port, dev);
+ zwitch_put(port->zs);
+ kfree(port);
+}
+
+static ssize_t vnet_port_read_mac(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct vnet_port *port;
+
+ port = container_of(dev, struct vnet_port, dev);
+ return sprintf(buf,"%02X:%02X:%02X:%02X:%02X:%02X", port->mac[0],
+ port->mac[1], port->mac[2], port->mac[3],
+ port->mac[4], port->mac[5]);
+}
+
+static ssize_t vnet_port_read_tx_bytes(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct vnet_port *port;
+
+ port = container_of(dev, struct vnet_port, dev);
+ return sprintf(buf,"%lu", port->tx_bytes);
+}
+
+static ssize_t vnet_port_read_rx_bytes(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct vnet_port *port;
+
+ port = container_of(dev, struct vnet_port, dev);
+ return sprintf(buf,"%lu", port->rx_bytes);
+}
+
+static ssize_t vnet_port_read_tx_packets(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct vnet_port *port;
+
+ port = container_of(dev, struct vnet_port, dev);
+ return sprintf(buf,"%lu", port->tx_packets);
+}
+
+static ssize_t vnet_port_read_rx_packets(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct vnet_port *port;
+
+ port = container_of(dev, struct vnet_port, dev);
+ return sprintf(buf,"%lu", port->rx_packets);
+}
+
+static ssize_t vnet_port_read_tx_dropped(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct vnet_port *port;
+
+ port = container_of(dev, struct vnet_port, dev);
+ return sprintf(buf,"%lu", port->tx_dropped);
+}
+
+static ssize_t vnet_port_read_rx_dropped(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct vnet_port *port;
+
+ port = container_of(dev, struct vnet_port, dev);
+ return sprintf(buf,"%lu", port->rx_dropped);
+}
+
+static DEVICE_ATTR(mac, S_IRUSR, vnet_port_read_mac, NULL);
+static DEVICE_ATTR(tx_bytes, S_IRUSR, vnet_port_read_tx_bytes, NULL);
+static DEVICE_ATTR(rx_bytes, S_IRUSR, vnet_port_read_rx_bytes, NULL);
+static DEVICE_ATTR(tx_packets, S_IRUSR, vnet_port_read_tx_packets, NULL);
+static DEVICE_ATTR(rx_packets, S_IRUSR, vnet_port_read_rx_packets, NULL);
+static DEVICE_ATTR(tx_dropped, S_IRUSR, vnet_port_read_tx_dropped, NULL);
+static DEVICE_ATTR(rx_dropped, S_IRUSR, vnet_port_read_rx_dropped, NULL);
+
+static int vnet_port_attributes(struct device *dev)
+{
+ int rc;
+ rc = device_create_file(dev, &dev_attr_mac);
+ if (rc)
+ return rc;
+ rc = device_create_file(dev, &dev_attr_tx_dropped);
+ if (rc)
+ return rc;
+ rc = device_create_file(dev, &dev_attr_rx_dropped);
+ if (rc)
+ return rc;
+ rc = device_create_file(dev, &dev_attr_rx_bytes);
+ if (rc)
+ return rc;
+ rc = device_create_file(dev, &dev_attr_tx_bytes);
+ if (rc)
+ return rc;
+ rc = device_create_file(dev, &dev_attr_rx_packets);
+ if (rc)
+ return rc;
+ rc = device_create_file(dev, &dev_attr_tx_packets);
+ return rc;
+}
+
+
+//FIXME implement this
+static int vnet_port_exists(struct vnet_switch *zs, char *name)
+{
+ read_lock(&zs->ports_lock);
+ read_unlock(&zs->ports_lock);
+ return 0;
+
+}
+
+static struct vnet_port *vnet_port_create(struct vnet_switch *zs,
+ char *name)
+{
+ struct vnet_port *port;
+
+ if (vnet_port_exists(zs, name))
+ return NULL;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (port) {
+ spin_lock_init(&port->rxlock);
+ spin_lock_init(&port->txlock);
+ INIT_LIST_HEAD(&port->lh);
+ port->zs = zs;
+ } else
+ return NULL;
+ port->dev.parent = &zs->dev;
+ port->dev.release = vnet_port_release;
+ strncpy(port->dev.bus_id, name, BUS_ID_SIZE);
+ if (device_register(&port->dev)) {
+ kfree(port);
+ return NULL;
+ }
+ if (vnet_port_attributes(&port->dev)) {
+ device_unregister(&port->dev);
+ kfree(port);
+ return NULL;
+ }
+ return port;
+}
+
+/*------------------------ switch creation/Destruction/housekeeping---------*/
+
+static void zwitch_destroy_ports(struct vnet_switch *zs)
+{
+ struct vnet_port *port, *tmp;
+
+ list_for_each_entry_safe(port, tmp, &zs->switch_ports, lh) {
+ if (port->destroy)
+ port->destroy(port);
+ else
+ printk("No destroy function for port\n");
+ }
+}
+
+
+static void zwitch_destroy(struct vnet_switch *zs)
+{
+ class_device_destroy(zwitch_class, zs->cdev.dev);
+ cdev_del(&zs->cdev);
+ device_unregister(&zs->dev);
+}
+
+static void zwitch_release(struct device *dev)
+{
+ struct vnet_switch *zs;
+
+ zs = container_of(dev, struct vnet_switch, dev);
+ kfree(zs);
+}
+
+static int __zwitch_get_minor(void)
+{
+ int d, found;
+ struct vnet_switch *zs;
+
+ for (d=0; d< NUM_MINORS; d++) {
+ found = 0;
+ list_for_each_entry(zs, &vnet_switches, lh)
+ if (MINOR(zs->cdev.dev) == d)
+ found++;
+ if (!found) break;
+ }
+ if (found) return -ENODEV;
+ return d;
+}
+
+/*
+ * checks if this name already exists for a zwitch
+ */
+static int __zwitch_check_name(char *name)
+{
+ struct vnet_switch *zs;
+
+ list_for_each_entry(zs, &vnet_switches, lh)
+ if (!strncmp(name, zs->name, ZWITCH_NAME_SIZE))
+ return -EEXIST;
+ return 0;
+}
+
+static int zwitch_create(char *name, int linktype)
+{
+ struct vnet_switch *zs;
+ int minor;
+ int ret;
+
+ if ((linktype < 2) || (linktype > 3))
+ return -EINVAL;
+ zs = kzalloc(sizeof(*zs), GFP_KERNEL);
+ if (!zs) {
+ printk("Creation of %s failed: out of memory\n", name);
+ return -ENOMEM;
+ }
+ zs->linktype = linktype;
+ strncpy(zs->name, name, ZWITCH_NAME_SIZE);
+ rwlock_init(&zs->ports_lock);
+ INIT_LIST_HEAD(&zs->switch_ports);
+
+ write_lock(&vnet_switches_lock);
+ minor = __zwitch_get_minor();
+ if (minor < 0) {
+ write_unlock(&vnet_switches_lock);
+ printk("Creation of %s failed: No free minor number\n", name);
+ kfree(zs);
+ return minor;
+ }
+ if (__zwitch_check_name(zs->name)) {
+ write_unlock(&vnet_switches_lock);
+ printk("Creation of %s failed: name exists\n", name);
+ kfree(zs);
+ return -EEXIST;
+ }
+ list_add_tail(&zs->lh, &vnet_switches);
+ write_unlock(&vnet_switches_lock);
+ strncpy(zs->dev.bus_id, name, min((int) strlen(name),
+ ZWITCH_NAME_SIZE));
+ zs->dev.parent = root_dev;
+ zs->dev.release = zwitch_release;
+ ret = device_register(&zs->dev);
+ if (ret) {
+ write_lock(&vnet_switches_lock);
+ list_del(&zs->lh);
+ write_unlock(&vnet_switches_lock);
+ printk("Creation of %s failed: no device\n",name);
+ return ret;
+ }
+ vnet_cdev_init(&zs->cdev);
+ cdev_add(&zs->cdev, MKDEV(vnet_major, minor), 1);
+ zs->class_device = class_device_create(zwitch_class, NULL,
+ zs->cdev.dev, &zs->dev, name);
+ if (IS_ERR(zs->class_device)) {
+ cdev_del(&zs->cdev);
+ write_lock(&vnet_switches_lock);
+ list_del(&zs->lh);
+ write_unlock(&vnet_switches_lock);
+ printk("Creation of %s failed: no class_device\n", name);
+ device_unregister(&zs->dev);
+ return PTR_ERR(zs->class_device);
+ }
+ return 0;
+}
+
+
+static int zwitch_delete(char *name)
+{
+ struct vnet_switch *zs;
+
+ write_lock(&vnet_switches_lock);
+ zs = __vnet_switch_by_name(name);
+ if (!zs) {
+ write_unlock(&vnet_switches_lock);
+ return -ENOENT;
+ }
+ list_del(&zs->lh);
+ write_unlock(&vnet_switches_lock);
+ zwitch_destroy_ports(zs);
+ zwitch_destroy(zs);
+ return 0;
+}
+
+/* checks if a switch for the given minor exists
+ * if yes, create an unconnected port on this switch
+ * if no, return NULL */
+struct vnet_port *vnet_port_get(int minor, char *port_name)
+{
+ struct vnet_switch *zs;
+ struct vnet_port *port;
+
+ zs = zwitch_get(minor);
+ if (!zs)
+ return NULL;
+ port = vnet_port_create(zs, port_name);
+ if (!port)
+ zwitch_put(zs);
+ return port;
+}
+
+/* attaches the port to the switch. The port must be
+ * fully initialized, as it may get called immediately afterwards */
+void vnet_port_attach(struct vnet_port *port)
+{
+ write_lock_bh(&port->zs->ports_lock);
+ __vnet_port_learn_macs(port);
+ list_add(&port->lh, &port->zs->switch_ports);
+ write_unlock_bh(&port->zs->ports_lock);
+ vnet_switch_add_mac(port);
+ return;
+}
+
+/* detaches the port from the switch. After that,
+ * no calls into the port are made */
+void vnet_port_detach(struct vnet_port *port)
+{
+ vnet_switch_del_mac(port);
+ write_lock_bh(&port->zs->ports_lock);
+ if (!list_empty(&port->lh))
+ list_del(&port->lh);
+ __vnet_port_unlearn_macs(port);
+ write_unlock_bh(&port->zs->ports_lock);
+}
+
+/* releases all ressources allocated with vnet_port_get */
+void vnet_port_put(struct vnet_port *port)
+{
+ BUG_ON(!list_empty(&port->lh) &&( port->lh.next != LIST_POISON1));
+ device_unregister(&port->dev);
+}
+
+/* tell the switch that new data is available */
+void vnet_port_rx(struct vnet_port *port)
+{
+ struct vnet_control *control;
+ int pkid, rc;
+
+ control = port->control;
+ if (vnet_q_empty(atomic_read(&control->p2smit))) {
+ printk(KERN_WARNING "vnet_switch: Empty buffer"
+ "on interrupt\n");
+ return;
+ }
+ do {
+ pkid = __nextr(atomic_read(&control->p2smit));
+ /* fire and forget. Let the switch care about lost packets*/
+ vnet_switch_packet(port, port->p2s_data[pkid],
+ control->p2sbufs[pkid].len,
+ control->p2sbufs[pkid].proto);
+ rc = vnet_rx_packet(&control->p2smit);
+ if (rc & QUEUE_WAS_FULL) {
+ port->interrupt(port, VNET_IRQ_START_TX);
+ }
+ } while (!(rc & QUEUE_IS_EMPTY));
+ return;
+}
+
+/* checks if the given address is locally attached to the switch*/
+int vnet_address_is_local(struct vnet_switch *zs, char *address)
+{
+ struct vnet_port *port;
+
+ read_lock(&zs->ports_lock);
+ port = __vnet_find_destination(zs, address);
+ read_unlock(&zs->ports_lock);
+ return (port != NULL);
+}
+
+
+int vnet_minor_by_name(char *name)
+{
+ struct vnet_switch *zs;
+ int ret;
+
+ read_lock(&vnet_switches_lock);
+ zs = __vnet_switch_by_name(name);
+ if (zs)
+ ret = MINOR(zs->cdev.dev);
+ else
+ ret = -ENODEV;
+ read_unlock(&vnet_switches_lock);
+ return ret;
+}
+
+static void vnet_root_release(struct device *dev)
+{
+ kfree(dev);
+}
+
+
+struct command {
+ char *string1;
+ char *string2;
+};
+
+/*FIXME this is ugly. Dont worry: as soon as we have finalized the interface,
+ this crap is going away. Still, it works.......*/
+static long vnet_control_ioctl(struct file *f, unsigned int command,
+ unsigned long data)
+{
+ char string1[BUS_ID_SIZE];
+ char string2[BUS_ID_SIZE];
+ struct command com;
+ struct vnet_port *port;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (copy_from_user(&com, (__user struct command*) data, sizeof(struct command)))
+ return -EFAULT;
+ if (copy_from_user(string1, (__user char *) com.string1, ZWITCH_NAME_SIZE))
+ return -EFAULT;
+ if (command >=2)
+ if (copy_from_user(string2, (__user char *) com.string2, ZWITCH_NAME_SIZE))
+ return -EFAULT;
+ if (strnlen(string1, ZWITCH_NAME_SIZE) == ZWITCH_NAME_SIZE)
+ return -EINVAL;
+ switch(command) {
+ case ADD_SWITCH:
+ return zwitch_create(string1,3);
+ case DEL_SWITCH:
+ return zwitch_delete(string1);
+ case ADD_HOST:
+ port = vnet_host_create(string1);
+ if (port) {
+ vnet_port_attach(port);
+ return 0;
+ } else
+ return -ENODEV;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vnet_control_open(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int vnet_control_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+struct file_operations vnet_control_fops = {
+ .open = vnet_control_open,
+ .release = vnet_control_release,
+ .unlocked_ioctl = &vnet_control_ioctl,
+ .compat_ioctl = &vnet_control_ioctl,
+};
+
+struct miscdevice vnet_control_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "vnet",
+ .fops = &vnet_control_fops,
+};
+
+int vnet_register_control_device(void)
+{
+ return misc_register(&vnet_control_device);
+}
+
+int __init vnet_switch_init(void)
+{
+ int ret;
+ dev_t dev;
+
+ zwitch_class = class_create(THIS_MODULE, "vnet");
+ if (IS_ERR(zwitch_class)) {
+ printk(KERN_ERR "vnet_switch: class_create failed!\n");
+ ret = PTR_ERR(zwitch_class);
+ goto out;
+ }
+ ret = alloc_chrdev_region(&dev, 0, NUM_MINORS, "vnet");
+ if (ret) {
+ printk(KERN_ERR "vnet_switch: alloc_chrdev_region failed\n");
+ goto out_class;
+ }
+ vnet_major = MAJOR(dev);
+ root_dev = kzalloc(sizeof(*root_dev), GFP_KERNEL);
+ if (!root_dev) {
+ printk(KERN_ERR "vnet_switch:allocation of device failed\n");
+ ret = -ENOMEM;
+ goto out_chrdev;
+ }
+ strncpy(root_dev->bus_id, "vnet", 5);
+ root_dev->release = vnet_root_release;
+ ret =device_register(root_dev);
+ if (ret) {
+ printk(KERN_ERR "vnet_switch: could not register device\n");
+ kfree(root_dev);
+ goto out_chrdev;
+ }
+ ret = vnet_register_control_device();
+ if (ret) {
+ printk("vnet_switch: could not create control device\n");
+ goto out_dev;
+ }
+ printk ("vnet_switch loaded\n");
+/* FIXME ---------- remove these static defines as soon as everyone has the
+ * user tools */
+ {
+ struct vnet_port *port;
+ zwitch_create("myswitch0",2);
+ zwitch_create("myswitch1",3);
+
+ port = vnet_host_create("myswitch0");
+ if (port)
+ vnet_port_attach(port);
+ port = vnet_host_create("myswitch1");
+ if (port)
+ vnet_port_attach(port);
+ }
+/*-----------------------------------------------------------*/
+ return 0;
+out_dev:
+ device_unregister(root_dev);
+out_chrdev:
+ unregister_chrdev_region(MKDEV(vnet_major,0), NUM_MINORS);
+out_class:
+ class_destroy(zwitch_class);
+out:
+ return ret;
+}
+
+/* remove all existing vnet_zwitches in the system and unregister the
+ * character device from the system */
+void vnet_switch_exit(void)
+{
+ struct vnet_switch *zs, *tmp;
+ list_for_each_entry_safe(zs, tmp, &vnet_switches, lh) {
+ zwitch_destroy_ports(zs);
+ zwitch_destroy(zs);
+ }
+ device_unregister(root_dev);
+ misc_deregister(&vnet_control_device);
+ unregister_chrdev_region(MKDEV(vnet_major,0), NUM_MINORS);
+ class_destroy(zwitch_class);
+ printk ("vnet_switch unloaded\n");
+}
+
+module_init(vnet_switch_init);
+module_exit(vnet_switch_exit);
+MODULE_DESCRIPTION("VNET: Virtual switch for vnet interfaces");
+MODULE_AUTHOR("Christian Borntraeger <borntrae-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>");
+MODULE_LICENSE("GPL");
Index: linux-2.6.21/drivers/s390/guest/vnet_switch.h
===================================================================
--- /dev/null
+++ linux-2.6.21/drivers/s390/guest/vnet_switch.h
@@ -0,0 +1,119 @@
+/*
+ * vnet_switch - zlive insular communication knack switch
+ * infrastructure for virtual switching of Linux guests running under Linux
+ *
+ * Copyright (C) 2005 IBM Corporation
+ * Author: Carsten Otte <cotte-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ * Christian Borntraeger <borntrae-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ *
+ */
+
+#ifndef __VNET_SWITCH_H
+#define __VNET_SWITCH_H
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/if_ether.h>
+#include <linux/spinlock.h>
+
+#include "vnet.h"
+
+/* defines for IOCTLs. interface should be replaced by something better */
+#define ADD_SWITCH 0
+#define DEL_SWITCH 1
+#define ADD_OSA 2
+#define DEL_OSA 3
+#define ADD_HOST 4
+#define DEL_HOST 5
+
+/* min(IFNAMSIZ, BUS_ID_SIZE)*/
+#define ZWITCH_NAME_SIZE 16
+
+/* This structure describes a virtual switch for ports to userspace network
+ * interfaces, e.g. in Linux under Linux environments*/
+struct vnet_switch {
+ struct list_head lh;
+ char name[ZWITCH_NAME_SIZE];
+ struct list_head switch_ports; /* list of ports */
+ rwlock_t ports_lock; /* lock for switch_ports */
+ struct class_device *class_device;
+ struct cdev cdev;
+ struct device dev;
+ struct vnet_port *osa;
+ int linktype; /* 2=ethernet 3=IP */
+};
+
+/* description of a port of the vnet_switch */
+struct vnet_port {
+ struct list_head lh;
+ struct vnet_switch *zs;
+ struct vnet_control *control;
+ void *s2p_data[VNET_QUEUE_LEN][(VNET_BUFFER_SIZE>>PAGE_SHIFT)];
+ void *p2s_data[VNET_QUEUE_LEN][(VNET_BUFFER_SIZE>>PAGE_SHIFT)];
+ char mac[ETH_ALEN];
+ void *priv;
+ int (*set_mac) (struct vnet_port *port, char mac[ETH_ALEN], int add);
+ void (*interrupt) (struct vnet_port *port, int type);
+ void (*destroy) (struct vnet_port *port);
+ struct device dev;
+ unsigned long rx_packets; /* total packets received */
+ unsigned long tx_packets; /* total packets transmitted */
+ unsigned long rx_bytes; /* total bytes received */
+ unsigned long tx_bytes; /* total bytes transmitted */
+ unsigned long rx_dropped; /* no space in receive buffer */
+ unsigned long tx_dropped; /* no space in destination buffer */
+ spinlock_t rxlock;
+ spinlock_t txlock;
+};
+
+
+static inline int
+vnet_copy_buf_to_pages(void **data, char *buf, int len)
+{
+ int i;
+
+ if (len == 0)
+ return 0;
+ for (i=0; i <= ((len - 1) >> PAGE_SHIFT); i++ )
+ memcpy(data[i], buf + i*PAGE_SIZE, min(PAGE_SIZE, len - i*PAGE_SIZE));
+ return len;
+}
+
+static inline int
+vnet_copy_pages_to_buf(char *buf, void **data, int len)
+{
+ int i;
+
+ if (len == 0)
+ return 0;
+ for (i=0; i <= ((len -1) >> PAGE_SHIFT); i++ )
+ memcpy(buf + i*PAGE_SIZE, data[i], min(PAGE_SIZE, len - i*PAGE_SIZE));
+ return len;
+}
+
+
+/* checks if a switch with the given minor exists
+ * if yes, create a named and unconnected port on
+ * this switch with the given name. if no, return NULL */
+extern struct vnet_port *vnet_port_get(int minor, char *port_name);
+
+/* attaches the port to the switch. The port must be
+ * fully initialized, as it may get data immediately afterwards */
+extern void vnet_port_attach(struct vnet_port *port);
+
+/* detaches the port from the switch. After that,
+ * no calls into the port are made */
+extern void vnet_port_detach(struct vnet_port *port);
+
+/* releases all ressources allocated with vnet_port_get */
+extern void vnet_port_put(struct vnet_port *port);
+
+/* tell the switch that new data is available */
+extern void vnet_port_rx(struct vnet_port *port);
+
+/* get the minor for a given name */
+extern int vnet_minor_by_name(char *name);
+
+/* checks if the given address is locally attached to the switch*/
+extern int vnet_address_is_local(struct vnet_switch *zs, char *address);
+#endif
Index: linux-2.6.21/drivers/s390/guest/Makefile
===================================================================
--- linux-2.6.21.orig/drivers/s390/guest/Makefile
+++ linux-2.6.21/drivers/s390/guest/Makefile
@@ -6,3 +6,6 @@ obj-$(CONFIG_GUEST_CONSOLE) += guest_con
obj-$(CONFIG_S390_GUEST) += vdev.o vdev_device.o
obj-$(CONFIG_VDISK) += vdisk.o vdisk_blk.o
obj-$(CONFIG_VNET_GUEST) += vnet_guest.o
+vnet_host-objs := vnet_switch.o vnet_port_guest.o vnet_port_host.o
+obj-$(CONFIG_VNET_HOST) += vnet_host.o
+
Index: linux-2.6.21/drivers/s390/net/Kconfig
===================================================================
--- linux-2.6.21.orig/drivers/s390/net/Kconfig
+++ linux-2.6.21/drivers/s390/net/Kconfig
@@ -95,4 +95,16 @@ config VNET_GUEST
connection.
If you're not using host/guest support, say N.

+config VNET_HOST
+ tristate "virtual networking support (HOST)"
+ depends on QETH && S390_HOST
+ help
+ This is the host part of the vnet guest network connection.
+ Say Y if you plan to host guests with network
+ connection. The host part consists of a virtual switch
+ a host device as well as a connection to the qeth
+ driver.
+ If you're not using this kernel for hosting guest, say N.
+
+
endmenu