[PATCH/RFC 7/9] Virtual network guest device driver

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

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

This is a work-in- progress paravirtualized network driver for Linux systems
running under a hypervisor.
The basic idea of this network driver is to have a shared memory pool between
host and guest. The guest allocates the buffer and registers its memory with
the host.

There are two queues one for guest to host traffic and vice versa. The queue
state is tracked via an 32 bit atomic. The first 16 bits indicate the slot in
the queue, where to put the next packet in, the last 16 bits indicate the slot
in the queue where to take the next packet out. Macros are provided to check
the queue for empty and full.

We use notification, when the queue _was_ empty or full. Guest to host
notification is done via the diagnose instruction. This is basically an
instruction for hypervisor call, similar to vmmcall and vmcall. For the
reverse notification the host sends an interrupt to the guest. Using NAPI, we
react on changes of the queue with netif_rx_schedule, netif_wake_queue,
netif_stop_queue and netif_rx_complete.
As the host only sends an interrupt if the queue was empty, we also need to
check for a race in the poll function and use netif_rx_reschedule. Otherwise
we would miss a wakup and would sleep forever.

Our s390 network drivers support cards that do IP packets only and provide no
MAC header at all. Therefore, the driver currently supports a layer2 based mode
(like ethernet) and a layer3 mode (we claim to be ptp) depending on the host.

So we have several prototypes and device drivers for paravirtualized network:
KVM, XEN, our prototype,lguest.... In the long term we want to have one driver
to rule^w drive them all, right?

This driver has currently some s390 specific aspects. I think we could get rid
of the diagnose calls with an architecture defined hypervisor call.

Please review, comments on the design are very welcome.

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 | 2
drivers/s390/guest/vnet.h | 147 +++++++++++
drivers/s390/guest/vnet_guest.c | 509 ++++++++++++++++++++++++++++++++++++++++
drivers/s390/guest/vnet_guest.h | 111 ++++++++
drivers/s390/net/Kconfig | 9
5 files changed, 777 insertions(+), 1 deletion(-)

Index: linux-2.6.21/drivers/s390/guest/vnet.h
===================================================================
--- /dev/null
+++ linux-2.6.21/drivers/s390/guest/vnet.h
@@ -0,0 +1,147 @@
+/*
+ * vnet - virtual networking for guest systems
+ *
+ * Copyright IBM Corp. 2007
+ * Authors: Carsten Otte <cotte-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ * Christian Borntraeger <borntrae-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ *
+ */
+
+#ifndef __VNET_H
+#define __VNET_H
+#include <linux/crc32.h>
+#include <linux/ioctl.h>
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <asm/atomic.h>
+#include <asm/page.h>
+
+#define VNET_MAJOR 12 //COFIXME
+
+#define VNET_QUEUE_LEN 80 // careful, vnet_control must be < PAGE
+#define VNET_BUFFER_SIZE 65536
+#define VNET_BUFFER_ORDER get_order(VNET_BUFFER_SIZE)
+#define VNET_BUFFER_PAGES (((VNET_BUFFER_SIZE-1)>>PAGE_SHIFT)+1)
+
+#define VNET_TIMEOUT (5*HZ)
+
+#define VNET_IRQ_START_RX 0
+#define VNET_IRQ_START_TX 1
+
+struct vnet_info {
+ int linktype;
+ int maxmtu;
+};
+
+#define VNET_IOCTL_ID 'Z'
+#define VNET_REGISTER_CTL _IOW(VNET_IOCTL_ID ,0, unsigned long long)
+#define VNET_INTERRUPT _IOW(VNET_IOCTL_ID, 1, int)
+#define VNET_INFO _IOR(VNET_IOCTL_ID, 2, struct vnet_info*)
+
+#define QUEUE_IS_EMPTY 1
+#define QUEUE_WAS_EMPTY 2
+#define QUEUE_IS_FULL 4
+#define QUEUE_WAS_FULL 8
+
+
+struct xmit_buffer {
+ __be16 len;
+ __be16 proto;
+ void *data;
+};
+
+struct vnet_control {
+ int buffer_size;
+ char mac[ETH_ALEN];
+ atomic_t p2smit __attribute__((__aligned__(SMP_CACHE_BYTES)));
+ atomic_t s2pmit __attribute__((__aligned__(SMP_CACHE_BYTES)));
+ struct xmit_buffer p2sbufs[VNET_QUEUE_LEN] __attribute__((__aligned__(SMP_CACHE_BYTES)));
+ struct xmit_buffer s2pbufs[VNET_QUEUE_LEN] __attribute__((__aligned__(SMP_CACHE_BYTES)));
+};
+
+
+#define __nextx(val) (((val) & 0xffff0000)>>16)
+#define __nextr(val) ((val) & 0xffff)
+#define __mkxr(x,r) ((((x) & 0xffff)<<16) | ((r) & 0xffff))
+
+static inline int
+vnet_q_empty(int val)
+{
+ return (__nextx(val) == __nextr(val));
+}
+
+static inline int
+vnet_q_half(int val)
+{
+ if (__nextx(val) == __nextr(val))
+ return 0;
+ if (__nextx(val) < __nextr(val)) {
+ if ((__nextr(val) - __nextx(val)) < VNET_QUEUE_LEN / 2)
+ return 1;
+ } else {
+ if ((__nextx(val) - __nextr(val)) > VNET_QUEUE_LEN / 2)
+ return 1;
+ }
+ return 0;
+}
+
+
+static inline int
+vnet_q_full(int val)
+{
+ if (__nextx(val) == __nextr(val) - 1)
+ return 1;
+ if ((__nextr(val) == 0) && (__nextx(val) == VNET_QUEUE_LEN - 1))
+ return 1;
+ return 0;
+}
+
+/* returns values:
+ * bit RX_QUEUE_NOW_EMPTY (1)
+ * and/or RX_QUEUE_WAS_FULL (2)
+ */
+static inline int
+vnet_rx_packet(atomic_t *ato)
+{
+ int oldval, newval, rc;
+
+ do {
+ oldval = atomic_read(ato);
+
+ //do we wrap?
+ if (__nextr(oldval)+1 == VNET_QUEUE_LEN)
+ newval = __mkxr(__nextx(oldval),0);
+ else
+ newval = __mkxr(__nextx(oldval),__nextr(oldval)+1);
+ } while (atomic_cmpxchg(ato, oldval, newval) != oldval);
+
+ rc = 0;
+ if (vnet_q_empty(newval))
+ rc |= QUEUE_IS_EMPTY;
+ if (vnet_q_full(oldval))
+ rc |= QUEUE_WAS_FULL;
+ return rc;
+}
+
+static inline int
+vnet_tx_packet(atomic_t *ato)
+{
+ int oldval, newval, rc;
+
+ do {
+ oldval = atomic_read(ato);
+
+ //do we wrap?
+ if (__nextx(oldval)+1 == VNET_QUEUE_LEN)
+ newval = __mkxr(0, __nextr(oldval));
+ else
+ newval = __mkxr(__nextx(oldval)+1,__nextr(oldval));
+ } while (atomic_cmpxchg(ato, oldval, newval) != oldval);
+ rc = 0;
+ if (vnet_q_empty(oldval))
+ rc |= QUEUE_WAS_EMPTY;
+ if (vnet_q_full(newval))
+ rc |= QUEUE_IS_FULL;
+ return rc;
+}
+#endif
Index: linux-2.6.21/drivers/s390/guest/vnet_guest.c
===================================================================
--- /dev/null
+++ linux-2.6.21/drivers/s390/guest/vnet_guest.c
@@ -0,0 +1,509 @@
+/*
+ * vnet - virtual network device driver
+ *
+ * Copyright IBM Corp. 2007
+ * Author: Carsten Otte <cotte-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ * Christian Borntraeger <borntrae-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <linux/rtnetlink.h>
+#include <linux/hardirq.h>
+#include <linux/mm.h>
+#include <linux/notifier.h>
+#include <asm/s390_ext.h>
+#include <asm/atomic.h>
+#include <asm/vdev.h>
+
+#include "vnet.h"
+#include "vnet_guest.h"
+
+static LIST_HEAD(vnet_devices);
+static rwlock_t vnet_devices_lock = RW_LOCK_UNLOCKED;
+
+
+static int
+vnet_net_open(struct net_device *dev)
+{
+ struct vnet_guest_device *guest;
+ struct vnet_control *control;
+
+ guest = netdev_priv(dev);
+ control = guest->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_guest_device *zk = netdev_priv(dev);
+ struct vnet_control *control = zk->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);
+ diag_vnet_send_interrupt(zk->hostfd, VNET_IRQ_START_RX);
+ netif_wake_queue(dev);
+}
+
+
+static void skbcopy(char *dest, struct sk_buff *skb)
+{
+ int i;
+
+ memcpy(dest, skb->data, skb_headlen(skb));
+ dest += skb_headlen(skb);
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ memcpy(dest, page_address(frag->page) +
+ frag->page_offset, frag->size);
+ dest+=frag->size;
+ }
+}
+
+static int
+vnet_net_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct vnet_guest_device *zk = netdev_priv(dev);
+ struct vnet_control *control = zk->control;
+ struct xmit_buffer *buf;
+ int pkid;
+ int buffer_status;
+
+ if (!spin_trylock(&zk->lock))
+ 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;
+ skbcopy(buf->data, skb);
+ buffer_status = vnet_tx_packet(&control->p2smit);
+ spin_unlock(&zk->lock);
+ zk->stats.tx_packets++;
+ zk->stats.tx_bytes += skb->len;
+ dev_kfree_skb_any(skb);
+ dev->trans_start = jiffies;
+ if (buffer_status & QUEUE_WAS_EMPTY)
+ diag_vnet_send_interrupt(zk->hostfd, VNET_IRQ_START_RX);
+ if (!(buffer_status & QUEUE_IS_FULL))
+ return NETDEV_TX_OK;
+ netif_stop_queue(dev);
+ spin_lock(&zk->lock);
+full:
+ if (!vnet_q_full(atomic_read(&control->p2smit)))
+ netif_start_queue(dev);
+ spin_unlock(&zk->lock);
+ return NETDEV_TX_OK;
+}
+
+static int
+vnet_l2_poll(struct net_device *dev, int *budget)
+{
+ struct vnet_guest_device *zk = netdev_priv(dev);
+ struct vnet_control *control = zk->control;
+ struct xmit_buffer *buf;
+ struct sk_buff *skb;
+ int pkid, count, numpackets = 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]; /* kernel pointer!*/
+ skb = dev_alloc_skb(buf->len);
+ if (likely(skb)) {
+ memcpy(skb_put(skb, buf->len), buf->data, buf->len);
+ skb->dev = dev;
+ skb->protocol = eth_type_trans(skb, dev);
+ zk->stats.rx_packets++;
+ zk->stats.rx_bytes += buf->len;
+ netif_receive_skb(skb);
+ numpackets--;
+ (*budget)--;
+ dev->quota--;
+ count++;
+ } else
+ zk->stats.rx_dropped++;
+ buffer_status = vnet_rx_packet(&control->s2pmit);
+ if (buffer_status & QUEUE_WAS_FULL)
+ diag_vnet_send_interrupt(zk->hostfd,
+ VNET_IRQ_START_TX);
+ 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; /* we're done for now */
+}
+
+
+static int
+vnet_l3_poll(struct net_device *dev, int *budget)
+{
+ struct vnet_guest_device *zk = dev->priv;
+ struct vnet_control *control = zk->control;
+ struct xmit_buffer *buf;
+ struct sk_buff *skb;
+ int pkid, count, numpackets = 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]; /*kernel pointer*/
+ skb = dev_alloc_skb(buf->len + NET_IP_ALIGN);
+ if (likely(skb)) {
+ skb_reserve(skb, NET_IP_ALIGN);
+ memcpy(skb_put(skb, buf->len), buf->data, buf->len);
+ skb->dev = dev;
+ skb->protocol = buf->proto;
+ skb->mac.raw = skb->data;
+ zk->stats.rx_packets++;
+ zk->stats.rx_bytes += buf->len;
+ netif_receive_skb(skb);
+ numpackets--;
+ (*budget)--;
+ dev->quota--;
+ count++;
+ } else
+ zk->stats.rx_dropped++;
+ buffer_status = vnet_rx_packet(&control->s2pmit);
+ if (buffer_status & QUEUE_WAS_FULL)
+ diag_vnet_send_interrupt(zk->hostfd,
+ VNET_IRQ_START_TX);
+ 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; /* we're done for now */
+}
+
+static struct net_device_stats *
+vnet_net_stats(struct net_device *dev)
+{
+ struct vnet_guest_device *zk = netdev_priv(dev);
+ return &zk->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_SG | 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);
+ dev->mtu = 1492;
+ dev->poll = vnet_l2_poll;
+ __vnet_common_init(dev);
+}
+
+static struct vnet_guest_device *
+__get_vnet_dev_by_fd(int fd)
+{
+ struct vnet_guest_device *zk;
+
+ read_lock(&vnet_devices_lock);
+ list_for_each_entry(zk, &vnet_devices, lh) {
+ if (zk->hostfd == fd)
+ goto found;
+ }
+ zk = NULL;
+ found:
+ read_unlock (&vnet_devices_lock);
+ return zk;
+}
+
+void vnet_ext_handler(__u16 code)
+{
+ unsigned int type = S390_lowcore.ext_params & 3;
+ unsigned int fd = S390_lowcore.ext_params >> 2;
+
+ struct vnet_guest_device *zk = __get_vnet_dev_by_fd(fd);
+
+ BUG_ON(!zk);
+ switch (type) {
+ case VNET_IRQ_START_RX:
+ netif_rx_schedule(zk->netdev);
+ break;
+ case VNET_IRQ_START_TX:
+ netif_wake_queue(zk->netdev);
+ break;
+ default:
+ BUG();
+ }
+}
+
+static void
+vnet_delete_device(struct vnet_guest_device *zd)
+{
+ int i;
+ unsigned long flags;
+
+ if (zd->hostfd >= 0)
+ diag_vnet_release(zd->hostfd);
+ write_lock_irqsave(&vnet_devices_lock, flags);
+ list_del(&zd->lh);
+ write_unlock_irqrestore(&vnet_devices_lock, flags);
+
+ for (i=0; i<VNET_QUEUE_LEN; i++) {
+ if (zd->control->s2pbufs[i].data) {
+ free_pages((unsigned long) zd->control->s2pbufs[i].data, VNET_BUFFER_ORDER);
+ zd->control->s2pbufs[i].data = NULL;
+ }
+ if (zd->control->p2sbufs[i].data) {
+ free_pages((unsigned long) zd->control->p2sbufs[i].data, VNET_BUFFER_ORDER);
+ zd->control->p2sbufs[i].data = NULL;
+ }
+ }
+ if (zd->control) {
+ kfree(zd->control);
+ zd->control = NULL;
+ }
+ if (zd->netdev) /* CAUTION: this also frees zd*/
+ free_netdev(zd->netdev);
+}
+
+
+static int vnet_device_alloc(struct vnet_guest_device *zd)
+{
+ int i;
+
+ zd->control = kzalloc(sizeof(struct vnet_control), GFP_KERNEL);
+ if (!zd->control)
+ return -ENOMEM;
+ for (i=0; i<VNET_QUEUE_LEN; i++) {
+ zd->control->s2pbufs[i].data = (void *) __get_free_pages(GFP_KERNEL, VNET_BUFFER_ORDER);
+ if (!zd->control->s2pbufs[i].data)
+ return -ENOMEM;
+ zd->control->p2sbufs[i].data = (void *) __get_free_pages(GFP_KERNEL, VNET_BUFFER_ORDER);
+ if (!zd->control->p2sbufs[i].data)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static int vnet_probe(struct vdev *vdev)
+{
+ int ret;
+ long flags;
+ struct vnet_guest_device *zd;
+ struct net_device *netdev;
+ int linktype;
+
+ if (strlen(vdev->symname) > IFNAMSIZ) {
+ printk(KERN_ERR "vnet: %s is too long for a network device,"
+ "discarding it\n", vdev->symname);
+ return -EINVAL;
+ }
+ ret = diag_vnet_info(vdev->hostid, &linktype);
+ if (ret)
+ return ret;
+ if (linktype == 3)
+ netdev = alloc_netdev(sizeof(*zd), vdev->symname,__vnet_layer3_init);
+ else
+ netdev = alloc_netdev(sizeof(*zd), vdev->symname, __vnet_layer2_init);
+ if (!netdev)
+ return -ENOMEM;
+ zd = netdev_priv(netdev);
+ zd->netdev = netdev;
+
+ ret =vnet_device_alloc(zd);
+ if (ret)
+ goto out;
+ zd->control->buffer_size = VNET_BUFFER_SIZE;
+ zd->linktype = linktype;
+ memcpy(zd->ifname, vdev->symname, IFNAMSIZ);
+ INIT_LIST_HEAD(&zd->lh);
+
+ write_lock_irqsave(&vnet_devices_lock, flags);
+ zd->hostfd = diag_vnet_open(vdev->hostid, zd->control);
+ if (zd->hostfd < 0) {
+ write_unlock_irqrestore(&vnet_devices_lock, flags);
+ goto out;
+ }
+ list_add_tail(&zd->lh, &vnet_devices);
+ write_unlock_irqrestore(&vnet_devices_lock, flags);
+
+ // host is ready, now we can set up our local network interface
+ rtnl_lock();
+ memcpy(netdev->dev_addr, zd->control->mac, 6);
+ spin_lock_init(&zd->lock);
+
+ if (!(ret = register_netdevice(zd->netdev))) {
+ /* good case */
+ rtnl_unlock();
+ printk("vnet: Successfully registered %s\n", vdev->symname);
+ return 0;
+ }
+ printk("vnet: Could not register network interface %s\n", vdev->symname);
+ rtnl_unlock();
+ out:
+ vnet_delete_device(zd);
+ return ret;
+}
+
+static struct vdev_driver vnet_driver = {
+ .name = "vnet",
+ .owner = THIS_MODULE,
+ .vdev_type = VDEV_TYPE_NET,
+ .probe = vnet_probe,
+};
+
+static int vnet_ip_event(struct notifier_block *this,
+ unsigned long event,void *ptr)
+{
+ struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
+ struct net_device *dev =(struct net_device *) ifa->ifa_dev->dev;
+ struct vnet_guest_device *zk;
+ read_lock(&vnet_devices_lock);
+ list_for_each_entry(zk, &vnet_devices, lh)
+ if (zk->netdev == dev) {
+ read_unlock(&vnet_devices_lock);
+ if (event == NETDEV_UP)
+ diag_vnet_ip(1, ifa->ifa_address,
+ ifa->ifa_mask,
+ ifa->ifa_broadcast);
+ if (event == NETDEV_DOWN)
+ diag_vnet_ip(0, ifa->ifa_address,
+ ifa->ifa_mask,
+ ifa->ifa_broadcast);
+ return NOTIFY_OK;
+ }
+ read_unlock(&vnet_devices_lock);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block vnet_ip_notifier = {
+ vnet_ip_event,
+ NULL
+};
+
+/* module related section */
+int
+vnet_guest_init(void)
+{
+ int ret;
+
+ if (!MACHINE_IS_GUEST)
+ return -ENODEV;
+ BUILD_BUG_ON(sizeof(struct vnet_control) > PAGE_SIZE);
+ register_external_interrupt(0x1236, vnet_ext_handler);
+ if(register_inetaddr_notifier(&vnet_ip_notifier)) {
+ printk(KERN_ERR "vnet: Could not register ip callback\n");
+ unregister_external_interrupt(0x1236, vnet_ext_handler);
+ }
+ ret = vdev_driver_register(&vnet_driver);
+ if (ret) {
+ printk(KERN_ERR "vnet: Could not register driver\n");
+ unregister_external_interrupt(0x1236, vnet_ext_handler);
+ unregister_inetaddr_notifier(&vnet_ip_notifier);
+ return ret;
+ }
+ return ret;
+}
+
+void
+vnet_guest_exit(void)
+{
+ struct vnet_guest_device *zk;
+ struct vnet_guest_device *temp;
+
+
+ unregister_external_interrupt(0x1236, vnet_ext_handler);
+ unregister_inetaddr_notifier(&vnet_ip_notifier);
+ rtnl_lock();
+ write_lock(&vnet_devices_lock);
+ list_for_each_entry_safe(zk, temp, &vnet_devices, lh) {
+ netif_stop_queue(zk->netdev);
+ unregister_netdevice(zk->netdev);
+ vnet_delete_device(zk);
+ }
+ write_unlock(&vnet_devices_lock);
+ rtnl_unlock();
+}
+
+module_init(vnet_guest_init);
+module_exit(vnet_guest_exit);
+MODULE_DESCRIPTION("VNET: Virtual network driver");
+MODULE_AUTHOR("Christian Borntraeger <borntrae-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>");
+MODULE_LICENSE("GPL");
Index: linux-2.6.21/drivers/s390/guest/vnet_guest.h
===================================================================
--- /dev/null
+++ linux-2.6.21/drivers/s390/guest/vnet_guest.h
@@ -0,0 +1,111 @@
+/*
+ * vnet - zlive insular communication knack
+ *
+ * Copyright (C) 2005 IBM Corporation
+ * Author: Carsten Otte <cotte-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ * Author: Christian Borntraeger <cborntra-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
+ *
+ */
+
+#ifndef __VNET_GUEST_H
+#define __VNET_GUEST_H
+
+#include <linux/netdevice.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include "vnet.h"
+
+
+struct vnet_guest_device {
+ struct list_head lh;
+ int hostfd;
+ char ifname[IFNAMSIZ];
+ struct net_device *netdev;
+ struct vnet_control *control;
+ struct net_device_stats stats;
+ struct work_struct work;
+ int linktype;
+ spinlock_t lock;
+};
+
+static inline int
+diag_vnet_info(char *ifname, int *linktype)
+{
+ register char * __arg1 asm ("2") = ifname;
+ register int * __arg2 asm ("3") = linktype;
+ register int __svcres asm("2");
+ int __res;
+
+ __asm__ __volatile__ (
+ "diag 0,0,0x0e"
+ : "=d" (__svcres)
+ : "0" (__arg1),
+ "d" (__arg2)
+ : "cc", "memory");
+ __res = __svcres;
+ return __res;
+}
+
+static inline int
+diag_vnet_open(char *ifname, struct vnet_control *ctrl)
+{
+ register char * __arg1 asm ("2") = ifname;
+ register struct vnet_control * __arg2 asm ("3") = ctrl;
+ register int __svcres asm("2");
+ int __res;
+
+ __asm__ __volatile__ (
+ "diag 0,0,0x0f"
+ : "=d" (__svcres)
+ : "0" (__arg1),
+ "d" (__arg2)
+ : "cc", "memory");
+ __res = __svcres;
+ return __res;
+}
+
+static inline void
+diag_vnet_send_interrupt(int fd, int type)
+{
+ register long __arg1 asm ("2") = fd;
+ register long __arg2 asm ("3") = type;
+
+ __asm__ __volatile__ (
+ "diag 0,0,0x11"
+ : : "d" (__arg1),
+ "d" (__arg2)
+ : "cc", "memory");
+}
+
+static inline void
+diag_vnet_release(int fd)
+{
+ register long __arg1 asm ("2") = fd;
+
+ __asm__ __volatile__ (
+ "diag 0,0,0x13"
+ : : "d" (__arg1)
+ : "cc", "memory");
+}
+static inline int
+diag_vnet_ip(int add, u32 addr, u32 mask, u32 broadcast)
+{
+ register long __arg1 asm ("2") = add;
+ register long __arg2 asm ("3") = addr;
+ register long __arg3 asm ("4") = mask;
+ register long __arg4 asm ("5") = broadcast;
+ register int __svcres asm("2");
+ int __res;
+
+ __asm__ __volatile__ (
+ "diag 0,0,0x1f"
+ : "=d" (__svcres)
+ : "d" (__arg1),
+ "d" (__arg2),
+ "d" (__arg3),
+ "d" (__arg4)
+ : "cc", "memory");
+ __res = __svcres;
+ return __res;
+}
+#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
@@ -5,4 +5,4 @@
obj-$(CONFIG_GUEST_CONSOLE) += guest_console.o guest_tty.o
obj-$(CONFIG_S390_GUEST) += vdev.o vdev_device.o
obj-$(CONFIG_VDISK) += vdisk.o vdisk_blk.o
-
+obj-$(CONFIG_VNET_GUEST) += vnet_guest.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
@@ -86,4 +86,13 @@ config CCWGROUP
tristate
default (LCS || CTC || QETH)

+config VNET_GUEST
+ tristate "virtual networking support (GUEST)"
+ depends on S390_GUEST
+ help
+ This is the guest part of the vnet guest network connection.
+ Say Y if you plan to run this kernel as guest with network
+ connection.
+ If you're not using host/guest support, say N.
+
endmenu