lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Tue, 16 Feb 2016 14:43:13 -0600
From:	Steven Royer <seroyer@...ux.vnet.ibm.com>
To:	Jonathan Corbet <corbet@....net>,
	Benjamin Herrenschmidt <benh@...nel.crashing.org>,
	Paul Mackerras <paulus@...ba.org>,
	Michael Ellerman <mpe@...erman.id.au>,
	Arnd Bergmann <arnd@...db.de>,
	Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
	linux-doc@...r.kernel.org, linux-kernel@...r.kernel.org,
	linuxppc-dev@...ts.ozlabs.org
Cc:	Steven Royer <seroyer@...ibm.com>,
	Steven Royer <seroyer@...ux.vnet.ibm.com>
Subject: [PATCH] add POWER Virtual Management Channel driver

From: Steven Royer <seroyer@...ibm.com>

The ibmvmc driver is a device driver for the POWER Virtual Management
Channel virtual adapter on the PowerVM platform.  It is used to
communicate with the hypervisor for virtualization management.  It
provides both request/response and asynchronous message support through
the /dev/ibmvmc node.

Signed-off-by: Steven Royer <seroyer@...ux.vnet.ibm.com>
---
This is used by the PowerVM NovaLink project.  You can see development history on github:
https://github.com/powervm/ibmvmc

 Documentation/ioctl/ioctl-number.txt |    1 +
 MAINTAINERS                          |    5 +
 arch/powerpc/include/asm/hvcall.h    |    3 +-
 drivers/misc/Kconfig                 |    9 +
 drivers/misc/Makefile                |    1 +
 drivers/misc/ibmvmc.c                | 1882 ++++++++++++++++++++++++++++++++++
 drivers/misc/ibmvmc.h                |  203 ++++
 7 files changed, 2103 insertions(+), 1 deletion(-)
 create mode 100644 drivers/misc/ibmvmc.c
 create mode 100644 drivers/misc/ibmvmc.h

diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index 91261a3..d5f5f4f 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -324,6 +324,7 @@ Code  Seq#(hex)	Include File		Comments
 0xCA	80-8F	uapi/scsi/cxlflash_ioctl.h
 0xCB	00-1F	CBM serial IEC bus	in development:
 					<mailto:michael.klein@...fin.lb.shuttle.de>
+0xCC	00-0F	drivers/misc/ibmvmc.h	pseries VMC driver
 0xCD	01	linux/reiserfs_fs.h
 0xCF	02	fs/cifs/ioctl.c
 0xDB	00-0F	drivers/char/mwave/mwavepub.h
diff --git a/MAINTAINERS b/MAINTAINERS
index cc2f753..c39dca2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5353,6 +5353,11 @@ L:	netdev@...r.kernel.org
 S:	Supported
 F:	drivers/net/ethernet/ibm/ibmvnic.*
 
+IBM Power Virtual Management Channel Driver
+M:	Steven Royer <seroyer@...ux.vnet.ibm.com>
+S:	Supported
+F:	drivers/misc/ibmvmc.*
+
 IBM Power Virtual SCSI Device Drivers
 M:	Tyrel Datwyler <tyreld@...ux.vnet.ibm.com>
 L:	linux-scsi@...r.kernel.org
diff --git a/arch/powerpc/include/asm/hvcall.h b/arch/powerpc/include/asm/hvcall.h
index e3b54dd..1ee6f2b 100644
--- a/arch/powerpc/include/asm/hvcall.h
+++ b/arch/powerpc/include/asm/hvcall.h
@@ -274,7 +274,8 @@
 #define H_COP			0x304
 #define H_GET_MPP_X		0x314
 #define H_SET_MODE		0x31C
-#define MAX_HCALL_OPCODE	H_SET_MODE
+#define H_REQUEST_VMC		0x360
+#define MAX_HCALL_OPCODE	H_REQUEST_VMC
 
 /* H_VIOCTL functions */
 #define H_GET_VIOA_DUMP_SIZE	0x01
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 054fc10..f8d9113 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -526,6 +526,15 @@ config VEXPRESS_SYSCFG
 	  bus. System Configuration interface is one of the possible means
 	  of generating transactions on this bus.
 
+config IBMVMC
+	tristate "IBM Virtual Management Channel support"
+	depends on PPC_PSERIES
+	help
+	  This is the IBM POWER Virtual Management Channel
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ibmvmc.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 537d7f3..08336b3 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE)		+= genwqe/
 obj-$(CONFIG_ECHO)		+= echo/
 obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)		+= cxl/
+obj-$(CONFIG_IBMVMC)		+= ibmvmc.o
diff --git a/drivers/misc/ibmvmc.c b/drivers/misc/ibmvmc.c
new file mode 100644
index 0000000..fb943b7
--- /dev/null
+++ b/drivers/misc/ibmvmc.c
@@ -0,0 +1,1882 @@
+/*
+ * IBM Power Systems Virtual Management Channel Support.
+ *
+ * Copyright (c) 2004, 2016 IBM Corp.
+ *   Dave Engebretsen engebret@...ibm.com
+ *   Steven Royer seroyer@...ux.vnet.ibm.com
+ *   Adam Reznechek adreznec@...ux.vnet.ibm.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/percpu.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/device.h>
+
+#include <asm/byteorder.h>
+#include <asm/irq.h>
+#include <asm/vio.h>
+
+#include "ibmvmc.h"
+
+#define IBMVMC_DRIVER_VERSION "1.0"
+
+MODULE_DESCRIPTION("IBM VMC");
+MODULE_AUTHOR("Steven Royer <seroyer@...ux.vnet.ibm.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(IBMVMC_DRIVER_VERSION);
+
+
+/*
+ * Static global variables
+ */
+static DECLARE_WAIT_QUEUE_HEAD(ibmvmc_read_wait);
+
+static const char ibmvmc_driver_name[] = "ibmvmc";
+static const char ibmvmc_workq_name[] = "ibmvmc";
+
+static struct ibmvmc_struct ibmvmc;
+static struct ibmvmc_hmc hmcs[MAX_HMCS];
+static struct crq_server_adapter ibmvmc_adapter;
+static dev_t ibmvmc_chrdev;
+
+static int ibmvmc_max_buf_pool_size = DEFAULT_BUF_POOL_SIZE;
+static int ibmvmc_max_hmcs = DEFAULT_HMCS;
+static int ibmvmc_max_mtu = DEFAULT_MTU;
+static struct class *ibmvmc_class;
+struct device *ibmvmc_dev;
+
+/* Module parameters */
+module_param_named(buf_pool_size, ibmvmc_max_buf_pool_size,
+		   int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(buf_pool_size, "Buffer pool size");
+module_param_named(max_hmcs, ibmvmc_max_hmcs, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(max_hmcs, "Max HMCs");
+module_param_named(max_mtu, ibmvmc_max_mtu, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(max_mtu, "Max MTU");
+
+
+static inline long h_copy_rdma(s64 length, u64 sliobn, u64 slioba,
+	u64 dliobn, u64 dlioba)
+{
+	long rc = 0;
+
+	/* Ensure all writes to source memory are visible before hcall */
+	mb();
+	pr_debug("ibmvmc: h_copy_rdma(0x%llx, 0x%llx, 0x%llx, 0x%llx, 0x%llx\n",
+			length, sliobn, slioba, dliobn, dlioba);
+	rc = plpar_hcall_norets(H_COPY_RDMA, length, sliobn, slioba,
+			dliobn, dlioba);
+	pr_debug("ibmvmc: h_copy_rdma rc = 0x%lx\n", rc);
+
+	return rc;
+}
+
+static inline void h_free_crq(uint32_t unit_address)
+{
+	long rc = 0;
+
+	do {
+		if (H_IS_LONG_BUSY(rc))
+			msleep(get_longbusy_msecs(rc));
+
+		rc = plpar_hcall_norets(H_FREE_CRQ, unit_address);
+	} while ((rc == H_BUSY) || (H_IS_LONG_BUSY(rc)));
+}
+
+/**
+ * h_request_vmc: - request a hypervisor virtual management channel device
+ * @vmc_index: drc index of the vmc device created
+ *
+ * Requests the hypervisor create a new virtual management channel device,
+ * allowing this partition to send hypervisor virtualization control commands.
+ *
+ */
+static inline long h_request_vmc(u32 *vmc_index)
+{
+	long rc = 0;
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE];
+
+	do {
+		if (H_IS_LONG_BUSY(rc))
+			msleep(get_longbusy_msecs(rc));
+
+		/* Call to request the VMC device from phyp */
+		rc = plpar_hcall(H_REQUEST_VMC, retbuf);
+		pr_debug("ibmvmc: h_request_vmc rc = 0x%lx\n", rc);
+		*vmc_index = retbuf[0];
+	} while ((rc == H_BUSY) || (H_IS_LONG_BUSY(rc)));
+
+	return rc;
+}
+
+/* routines for managing a command/response queue */
+/**
+ * ibmvmc_handle_event: - Interrupt handler for crq events
+ * @irq:        number of irq to handle, not used
+ * @dev_instance: crq_server_adapter that received interrupt
+ *
+ * Disables interrupts and schedules ibmvmc_task
+ * Always returns IRQ_HANDLED
+ */
+static irqreturn_t ibmvmc_handle_event(int irq, void *dev_instance)
+{
+	struct crq_server_adapter *adapter =
+		(struct crq_server_adapter *)dev_instance;
+
+	vio_disable_interrupts(to_vio_dev(adapter->dev));
+	queue_work(adapter->work_queue, &adapter->work);
+
+	return IRQ_HANDLED;
+}
+
+static void ibmvmc_release_crq_queue(struct crq_server_adapter *adapter)
+{
+	struct vio_dev *vdev = to_vio_dev(adapter->dev);
+	struct crq_queue *queue = &adapter->queue;
+
+	free_irq(vdev->irq, (void *)adapter);
+	flush_workqueue(adapter->work_queue);
+	destroy_workqueue(adapter->work_queue);
+
+	h_free_crq(vdev->unit_address);
+	dma_unmap_single(adapter->dev,
+			queue->msg_token,
+			queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL);
+	free_page((unsigned long)queue->msgs);
+}
+
+static int ibmvmc_reset_crq_queue(struct crq_server_adapter *adapter)
+{
+	int rc = 0;
+	struct vio_dev *vdev = to_vio_dev(adapter->dev);
+	struct crq_queue *queue = &adapter->queue;
+
+	/* Close the CRQ */
+	h_free_crq(vdev->unit_address);
+
+	/* Clean out the queue */
+	memset(queue->msgs, 0x00, PAGE_SIZE);
+	queue->cur = 0;
+
+	/* And re-open it again */
+	rc = plpar_hcall_norets(H_REG_CRQ,
+				vdev->unit_address,
+				queue->msg_token, PAGE_SIZE);
+	if (rc == 2)
+		/* Adapter is good, but other end is not ready */
+		pr_warn("ibmvmc: Partner adapter not ready\n");
+	else if (rc != 0)
+		pr_err("ibmvmc: couldn't register crq--rc 0x%x\n", rc);
+
+	return rc;
+}
+
+/**
+ * crq_queue_next_crq: - Returns the next entry in message queue
+ * @queue:      crq_queue to use
+ *
+ * Returns pointer to next entry in queue, or NULL if there are no new
+ * entried in the CRQ.
+ */
+static struct crq_msg_ibmvmc *crq_queue_next_crq(struct crq_queue *queue)
+{
+	struct crq_msg_ibmvmc *crq;
+	unsigned long flags;
+
+	spin_lock_irqsave(&queue->lock, flags);
+	crq = &queue->msgs[queue->cur];
+	if (crq->valid & 0x80) {
+		if (++queue->cur == queue->size)
+			queue->cur = 0;
+
+		/* Ensure the read of the valid bit occurs before reading any
+		 * other bits of the CRQ entry
+		 */
+		rmb();
+	} else
+		crq = NULL;
+
+	spin_unlock_irqrestore(&queue->lock, flags);
+
+	return crq;
+}
+
+static long ibmvmc_send_crq(struct crq_server_adapter *adapter,
+			u64 word1, u64 word2)
+{
+	long rc;
+	struct vio_dev *vdev = to_vio_dev(adapter->dev);
+
+	pr_debug("ibmvmc: ibmvmc_send_crq(0x%x, 0x%016llx, 0x%016llx)\n",
+			vdev->unit_address, word1, word2);
+
+	/*
+	 * Ensure the command buffer is flushed to memory before handing it
+	 * over to the other side to prevent it from fetching any stale data.
+	 */
+	mb();
+	rc = plpar_hcall_norets(H_SEND_CRQ, vdev->unit_address, word1, word2);
+	pr_debug("ibmvmc: ibmvmc_send_crq rc = 0x%lx\n", rc);
+
+	return rc;
+}
+
+static void *alloc_dma_buffer(struct vio_dev *vdev, size_t size,
+				dma_addr_t *dma_handle)
+{
+	/* allocate memory */
+	void *buffer = kzalloc(size, GFP_KERNEL);
+
+	if (!buffer) {
+		*dma_handle = 0;
+		return NULL;
+	}
+
+	/* DMA map */
+	*dma_handle = dma_map_single(&vdev->dev, buffer, size,
+				    DMA_BIDIRECTIONAL);
+
+	if (dma_mapping_error(&vdev->dev, *dma_handle)) {
+		*dma_handle = 0;
+		kzfree(buffer);
+		return NULL;
+	}
+
+	return buffer;
+}
+
+static void free_dma_buffer(struct vio_dev *vdev, size_t size, void *vaddr,
+			    dma_addr_t dma_handle)
+{
+	/* DMA unmap */
+	dma_unmap_single(&vdev->dev, dma_handle, size, DMA_BIDIRECTIONAL);
+
+	/* deallocate memory */
+	kzfree(vaddr);
+}
+
+static struct ibmvmc_buffer *get_valid_hmc_buffer_locked(u8 hmc_index)
+{
+	unsigned long i;
+	struct ibmvmc_buffer *buffer;
+	struct ibmvmc_buffer *ret_buf = NULL;
+
+	if (hmc_index > ibmvmc.max_hmc_index)
+		return NULL;
+
+	buffer = hmcs[hmc_index].buffer;
+
+	for (i = 0; i < ibmvmc_max_buf_pool_size; i++) {
+		if ((buffer[i].valid) && (buffer[i].free) &&
+				(buffer[i].owner == VMC_BUF_OWNER_ALPHA)) {
+			buffer[i].free = 0;
+			ret_buf = &(buffer[i]);
+			break;
+		}
+	}
+
+	return ret_buf;
+}
+
+static struct ibmvmc_buffer *get_free_hmc_buffer_locked(u8 hmc_index)
+{
+	unsigned long i;
+	struct ibmvmc_buffer *buffer;
+	struct ibmvmc_buffer *ret_buf = NULL;
+
+	if (hmc_index > ibmvmc.max_hmc_index) {
+		pr_info("ibmvmc: get_free_hmc_buffer: invalid hmc_index=0x%x\n",
+				hmc_index);
+		return NULL;
+	}
+
+	buffer = hmcs[hmc_index].buffer;
+
+	for (i = 0; i < ibmvmc_max_buf_pool_size; i++) {
+		if ((buffer[i].free) &&
+				(buffer[i].owner == VMC_BUF_OWNER_ALPHA)) {
+			buffer[i].free = 0;
+			ret_buf = &(buffer[i]);
+			break;
+		}
+	}
+
+	return ret_buf;
+}
+
+static void return_hmc_buffer(struct ibmvmc_hmc *hmc,
+		struct ibmvmc_buffer *buffer)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&(hmc->lock), flags);
+	buffer->free = 1;
+	spin_unlock_irqrestore(&(hmc->lock), flags);
+}
+
+static void count_hmc_buffers(u8 hmc_index, unsigned int *valid,
+		unsigned int *free)
+{
+	unsigned long i;
+	struct ibmvmc_buffer *buffer;
+	unsigned long flags;
+
+	if (hmc_index > ibmvmc.max_hmc_index)
+		return;
+
+	if (valid == NULL || free == NULL)
+		return;
+
+	*valid = 0; *free = 0;
+
+	buffer = hmcs[hmc_index].buffer;
+	spin_lock_irqsave(&(hmcs[hmc_index].lock), flags);
+
+	for (i = 0; i < ibmvmc_max_buf_pool_size; i++) {
+		if (buffer[i].valid) {
+			*valid = *valid + 1;
+			if (buffer[i].free)
+				*free = *free + 1;
+		}
+	}
+
+	spin_unlock_irqrestore(&(hmcs[hmc_index].lock), flags);
+}
+
+static struct ibmvmc_hmc *ibmvmc_get_free_hmc(void)
+{
+	unsigned long i;
+	unsigned long flags;
+
+	/*
+	 * Find an available HMC connection.
+	 */
+	for (i = 0; i <= ibmvmc.max_hmc_index; i++) {
+		spin_lock_irqsave(&(hmcs[i].lock), flags);
+		if (hmcs[i].state == ibmhmc_state_free) {
+			hmcs[i].index = i;
+			hmcs[i].state = ibmhmc_state_initial;
+			spin_unlock_irqrestore(&(hmcs[i].lock), flags);
+			return &hmcs[i];
+		}
+		spin_unlock_irqrestore(&(hmcs[i].lock), flags);
+	}
+
+	return NULL;
+}
+
+static int ibmvmc_return_hmc(struct ibmvmc_hmc *hmc, bool bReleaseReaders)
+{
+	unsigned long i;
+	struct ibmvmc_buffer *buffer;
+	struct crq_server_adapter *adapter;
+	struct vio_dev *vdev;
+	unsigned long flags;
+
+	if ((hmc == NULL) || (hmc->adapter == NULL))
+		return -EIO;
+
+	if (bReleaseReaders) {
+		if (hmc->pSession != NULL) {
+			struct ibmvmc_file_session *session = hmc->pSession;
+
+			session->valid = 0;
+			wake_up_interruptible(&ibmvmc_read_wait);
+		}
+	}
+
+	adapter = hmc->adapter;
+	vdev = to_vio_dev(adapter->dev);
+
+	spin_lock_irqsave(&(hmc->lock), flags);
+	hmc->index = 0;
+	hmc->state = ibmhmc_state_free;
+	hmc->queue_head = 0;
+	hmc->queue_tail = 0;
+	buffer = hmc->buffer;
+	for (i = 0; i < ibmvmc_max_buf_pool_size; i++) {
+		if (buffer[i].valid) {
+			free_dma_buffer(vdev,
+					    ibmvmc.max_mtu,
+					    buffer[i].real_addr_local,
+					    buffer[i].dma_addr_local);
+			pr_debug("ibmvmc: Forgot buffer id 0x%lx\n", i);
+		}
+		memset(&buffer[i], 0, sizeof(struct ibmvmc_buffer));
+
+		hmc->queue_outbound_msgs[i] = VMC_INVALID_BUFFER_ID;
+	}
+
+	spin_unlock_irqrestore(&(hmc->lock), flags);
+
+	return 0;
+}
+
+static int send_open(struct ibmvmc_buffer *buffer,
+		     struct ibmvmc_hmc *hmc)
+{
+	int rc = 0;
+	struct crq_msg_ibmvmc crq_msg;
+	__be64 *crq_as_u64 = (__be64 *)&crq_msg;
+	struct crq_server_adapter *adapter;
+
+	if ((hmc == NULL) || (hmc->adapter == NULL))
+		return -EIO;
+
+	adapter = hmc->adapter;
+
+	pr_debug("ibmvmc: send_open: 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
+	     (unsigned long)buffer->size, (unsigned long)adapter->liobn,
+	     (unsigned long)buffer->dma_addr_local,
+	     (unsigned long)adapter->riobn,
+	     (unsigned long)buffer->dma_addr_remote);
+
+	rc = h_copy_rdma(buffer->size,
+			 adapter->liobn,
+			 buffer->dma_addr_local,
+			 adapter->riobn,
+			 buffer->dma_addr_remote);
+	if (rc) {
+		pr_err("ibmvmc: Error: In send_open, h_copy_rdma rc 0x%x\n",
+				rc);
+		return -EIO;
+	}
+
+	hmc->state = ibmhmc_state_opening;
+
+	crq_msg.valid = 0x80;
+	crq_msg.type = VMC_MSG_OPEN;
+	crq_msg.status = 0;
+	crq_msg.var1.rsvd = 0;
+	crq_msg.hmc_session = hmc->session;
+	crq_msg.hmc_index = hmc->index;
+	crq_msg.var2.buffer_id = cpu_to_be16(buffer->id);
+	crq_msg.rsvd = 0;
+	crq_msg.var3.rsvd = 0;
+
+	ibmvmc_send_crq(adapter, be64_to_cpu(crq_as_u64[0]),
+			be64_to_cpu(crq_as_u64[1]));
+
+	return rc;
+}
+
+static int send_close(struct ibmvmc_hmc *hmc)
+{
+	int rc = 0;
+	struct crq_msg_ibmvmc crq_msg;
+	__be64 *crq_as_u64 = (__be64 *)&crq_msg;
+	struct crq_server_adapter *adapter;
+
+	pr_info("ibmvmc: CRQ send: close\n");
+
+	if ((hmc == NULL) || (hmc->adapter == NULL))
+		return -EIO;
+
+	adapter = hmc->adapter;
+
+	crq_msg.valid = 0x80;
+	crq_msg.type = VMC_MSG_CLOSE;
+	crq_msg.status = 0;
+	crq_msg.var1.rsvd = 0;
+	crq_msg.hmc_session = hmc->session;
+	crq_msg.hmc_index = hmc->index;
+	crq_msg.var2.rsvd = 0;
+	crq_msg.rsvd = 0;
+	crq_msg.var3.rsvd = 0;
+
+	ibmvmc_send_crq(adapter, be64_to_cpu(crq_as_u64[0]),
+			be64_to_cpu(crq_as_u64[1]));
+
+	return rc;
+}
+
+static int ibmvmc_send_capabilities(struct crq_server_adapter *adapter)
+{
+	struct crq_msg_ibmvmc_admin crq_msg;
+	__be64 *crq_as_u64 = (__be64 *)&crq_msg;
+
+	pr_debug("ibmvmc: CRQ send: capabilities\n");
+	crq_msg.valid = 0x80;
+	crq_msg.type = VMC_MSG_CAP;
+	crq_msg.status = 0;
+	crq_msg.rsvd[0] = 0;
+	crq_msg.rsvd[1] = 0;
+	crq_msg.max_hmc = ibmvmc_max_hmcs;
+	crq_msg.max_mtu = cpu_to_be32(ibmvmc_max_mtu);
+	crq_msg.pool_size = cpu_to_be16(ibmvmc_max_buf_pool_size);
+	crq_msg.crq_size = cpu_to_be16(adapter->queue.size);
+	crq_msg.version = cpu_to_be16(IBMVMC_PROTOCOL_VERSION);
+
+	ibmvmc_send_crq(adapter, be64_to_cpu(crq_as_u64[0]),
+			be64_to_cpu(crq_as_u64[1]));
+
+	ibmvmc.state = ibmvmc_state_capabilities;
+
+	return 0;
+}
+
+static int ibmvmc_send_add_buffer_resp(struct crq_server_adapter *adapter,
+		u8 status, u8 hmc_session, u8 hmc_index, u16 buffer_id)
+{
+	struct crq_msg_ibmvmc crq_msg;
+	__be64 *crq_as_u64 = (__be64 *)&crq_msg;
+
+	pr_debug("ibmvmc: CRQ send: add_buffer_resp\n");
+	crq_msg.valid = 0x80;
+	crq_msg.type = VMC_MSG_ADD_BUF_RESP;
+	crq_msg.status = status;
+	crq_msg.var1.rsvd = 0;
+	crq_msg.hmc_session = hmc_session;
+	crq_msg.hmc_index = hmc_index;
+	crq_msg.var2.buffer_id = cpu_to_be16(buffer_id);
+	crq_msg.rsvd = 0;
+	crq_msg.var3.rsvd = 0;
+
+	ibmvmc_send_crq(adapter, be64_to_cpu(crq_as_u64[0]),
+			be64_to_cpu(crq_as_u64[1]));
+
+	return 0;
+}
+
+static int ibmvmc_send_rem_buffer_resp(struct crq_server_adapter *adapter,
+		u8 status, u8 hmc_session, u8 hmc_index, u16 buffer_id)
+{
+	struct crq_msg_ibmvmc crq_msg;
+	__be64 *crq_as_u64 = (__be64 *)&crq_msg;
+
+	pr_debug("ibmvmc: CRQ send: rem_buffer_resp\n");
+	crq_msg.valid = 0x80;
+	crq_msg.type = VMC_MSG_REM_BUF_RESP;
+	crq_msg.status = status;
+	crq_msg.var1.rsvd = 0;
+	crq_msg.hmc_session = hmc_session;
+	crq_msg.hmc_index = hmc_index;
+	crq_msg.var2.buffer_id = cpu_to_be16(buffer_id);
+	crq_msg.rsvd = 0;
+	crq_msg.var3.rsvd = 0;
+
+	ibmvmc_send_crq(adapter, be64_to_cpu(crq_as_u64[0]),
+			be64_to_cpu(crq_as_u64[1]));
+
+	return 0;
+}
+
+static int send_msg(struct crq_server_adapter *adapter,
+		    struct ibmvmc_buffer *buffer,
+		    struct ibmvmc_hmc *hmc, int msg_len)
+{
+	int rc = 0;
+	struct crq_msg_ibmvmc crq_msg;
+	__be64 *crq_as_u64 = (__be64 *)&crq_msg;
+
+	pr_debug("ibmvmc: CRQ send: rdma to HV\n");
+	rc = h_copy_rdma(msg_len,
+			 adapter->liobn,
+			 buffer->dma_addr_local,
+			 adapter->riobn,
+			 buffer->dma_addr_remote);
+	if (rc) {
+		pr_err("ibmvmc: Error in send_msg, h_copy_rdma rc 0x%x\n", rc);
+		return rc;
+	}
+
+	crq_msg.valid = 0x80;
+	crq_msg.type = VMC_MSG_SIGNAL;
+	crq_msg.status = 0;
+	crq_msg.var1.rsvd = 0;
+	crq_msg.hmc_session = hmc->session;
+	crq_msg.hmc_index = hmc->index;
+	crq_msg.var2.buffer_id = cpu_to_be16(buffer->id);
+	crq_msg.var3.msg_len = cpu_to_be32(msg_len);
+	pr_debug("ibmvmc: CRQ send: msg to HV 0x%llx 0x%llx\n",
+			be64_to_cpu(crq_as_u64[0]), be64_to_cpu(crq_as_u64[1]));
+
+	buffer->owner = VMC_BUF_OWNER_HV;
+	ibmvmc_send_crq(adapter, be64_to_cpu(crq_as_u64[0]),
+			be64_to_cpu(crq_as_u64[1]));
+
+	return rc;
+}
+
+static int ibmvmc_open(struct inode *inode, struct file *file)
+{
+	int retval = 0;
+	struct ibmvmc_file_session *session;
+
+	pr_debug("ibmvmc_open: inode = 0x%lx, file = 0x%lx, state = 0x%x\n",
+			(unsigned long)inode, (unsigned long)file,
+			ibmvmc.state);
+
+	session = kzalloc(sizeof(struct ibmvmc_file_session), GFP_KERNEL);
+	session->file = file;
+	file->private_data = session;
+
+	return retval;
+}
+
+static int ibmvmc_close(struct inode *inode, struct file *file)
+{
+	int rc = 0;
+	struct ibmvmc_file_session *session;
+	struct ibmvmc_hmc *hmc;
+
+	pr_debug("ibmvmc_close: file = 0x%lx, state = 0x%x\n",
+	     (unsigned long)file, ibmvmc.state);
+
+	session = file->private_data;
+	if (!session)
+		return -EIO;
+
+	hmc = session->hmc;
+	if (hmc) {
+		if (!hmc->adapter)
+			return -EIO;
+
+		if (ibmvmc.state == ibmvmc_state_failed) {
+			pr_warn("ibmvmc: close: state_failed\n");
+			return -EIO;
+		}
+
+		/* TODO locking? */
+		if (hmc->state >= ibmhmc_state_opening) {
+			rc = send_close(hmc);
+			if (rc)
+				pr_warn("ibmvmc: close: send_close failed.\n");
+		}
+	}
+
+	kzfree(session);
+
+	return rc;
+}
+
+static ssize_t ibmvmc_read(struct file *file, char *buf, size_t nbytes,
+		loff_t *ppos)
+{
+	DEFINE_WAIT(wait);
+	ssize_t	n;
+	struct ibmvmc_file_session *session;
+	struct ibmvmc_hmc *hmc;
+	struct crq_server_adapter *adapter;
+	struct ibmvmc_buffer *buffer;
+	ssize_t retval = 0;
+	unsigned long flags;
+
+	pr_debug("ibmvmc: read: file = 0x%lx, buf = 0x%lx, nbytes = 0x%lx\n",
+	     (unsigned long)file, (unsigned long) buf, (unsigned long)nbytes);
+
+	if (nbytes == 0)
+		return 0;
+
+	if (nbytes > ibmvmc.max_mtu) {
+		pr_warn("ibmvmc: read: nbytes invalid 0x%x\n",
+				(unsigned int)nbytes);
+		return -EINVAL;
+	}
+
+	session = file->private_data;
+	if (!session) {
+		pr_warn("ibmvmc: read: no session\n");
+		return -EIO;
+	}
+
+	hmc = session->hmc;
+	if (!hmc) {
+		pr_warn("ibmvmc: read: no hmc\n");
+		return -EIO;
+	}
+
+	adapter = hmc->adapter;
+	if (!adapter) {
+		pr_warn("ibmvmc: read: no adapter\n");
+		return -EIO;
+	}
+
+	do {
+		prepare_to_wait(&ibmvmc_read_wait, &wait, TASK_INTERRUPTIBLE);
+
+		spin_lock_irqsave(&(hmc->lock), flags);
+		if (hmc->queue_tail != hmc->queue_head)
+			/* Data is available */
+			break;
+
+		spin_unlock_irqrestore(&(hmc->lock), flags);
+
+		if (!session->valid) {
+			retval = -EBADFD;
+			goto out;
+		}
+		if (file->f_flags & O_NONBLOCK) {
+			retval = -EAGAIN;
+			goto out;
+		}
+
+		schedule();
+
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			goto out;
+		}
+	} while (1);
+
+	buffer = &(hmc->buffer[hmc->queue_outbound_msgs[hmc->queue_tail]]);
+	hmc->queue_tail++;
+	if (hmc->queue_tail == ibmvmc_max_buf_pool_size)
+		hmc->queue_tail = 0;
+	spin_unlock_irqrestore(&(hmc->lock), flags);
+
+	nbytes = min_t(size_t, nbytes, buffer->msg_len);
+	n = copy_to_user((void *)buf, buffer->real_addr_local, nbytes);
+	pr_debug("ibmvmc: read: copy to user nbytes = 0x%lx.\n", nbytes);
+	return_hmc_buffer(hmc, buffer);
+	retval = nbytes;
+
+	if (n) {
+		pr_warn("ibmvmc: read: copy to user failed.\n");
+		retval = -EFAULT;
+	}
+
+ out:
+	finish_wait(&ibmvmc_read_wait, &wait);
+	pr_debug("ibmvmc: read: out %ld\n", retval);
+	return retval;
+}
+
+static unsigned int ibmvmc_poll(struct file *file, poll_table *wait)
+{
+	unsigned int mask = 0;
+	struct ibmvmc_file_session *session;
+	struct ibmvmc_hmc *hmc;
+
+	session = file->private_data;
+	if (!session)
+		return 0;
+
+	hmc = session->hmc;
+	if (!hmc)
+		return 0;
+
+	poll_wait(file, &ibmvmc_read_wait, wait);
+
+	if (hmc->queue_head != hmc->queue_tail)
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+static ssize_t ibmvmc_write(struct file *file, const char *buffer,
+	  size_t count, loff_t *ppos)
+{
+	int		ret = 0;
+	size_t		bytes;
+	const char	*p = buffer;
+	size_t		c = count;
+	struct ibmvmc_buffer *vmc_buffer;
+	unsigned char *buf;
+	struct ibmvmc_file_session *session;
+	struct crq_server_adapter *adapter;
+	struct ibmvmc_hmc *hmc;
+	unsigned long flags;
+
+	session = file->private_data;
+	if (!session)
+		return -EIO;
+
+	hmc = session->hmc;
+	if (!hmc)
+		return -EIO;
+
+	spin_lock_irqsave(&(hmc->lock), flags);
+	if (hmc->state == ibmhmc_state_free) {
+		/* HMC connection is not valid (possibly was reset under us). */
+		ret = -EIO;
+		goto out;
+	}
+
+	adapter = hmc->adapter;
+	if (!adapter) {
+		ret = -EIO;
+		goto out;
+	}
+
+	if (count > ibmvmc.max_mtu) {
+		pr_warn("ibmvmc_write: invalid buffer size 0x%lx\n",
+		     (unsigned long)count);
+		ret = -EIO;
+		goto out;
+	}
+
+	/* Waiting for the open resp message to the ioctl(1) - retry */
+	if (hmc->state == ibmhmc_state_opening) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Make sure the ioctl() was called & the open msg sent, and that
+	 * the HMC connection has not failed.
+	 */
+	if (hmc->state != ibmhmc_state_ready) {
+		ret = -EIO;
+		goto out;
+	}
+
+	vmc_buffer = get_valid_hmc_buffer_locked(hmc->index);
+	if (vmc_buffer == NULL) {
+		/* No buffer available for the msg send, or we have not yet
+		 * completed the open/open_resp sequence.  Retry until this is
+		 * complete.
+		 */
+		ret = -EBUSY;
+		goto out;
+	}
+	if (vmc_buffer->real_addr_local == NULL) {
+		pr_err("ibmvmc_write: no buffer storage assigned\n");
+		ret = -EIO;
+		goto out;
+	}
+	buf = vmc_buffer->real_addr_local;
+
+	while (c > 0) {
+		bytes = min_t(size_t, c, vmc_buffer->size);
+
+		bytes -= copy_from_user(buf, p, bytes);
+		if (!bytes) {
+			ret = -EFAULT;
+			goto out;
+		}
+		c -= bytes;
+		p += bytes;
+	}
+	if (p == buffer)
+		goto out;
+
+	file->f_path.dentry->d_inode->i_mtime = CURRENT_TIME;
+	mark_inode_dirty(file->f_path.dentry->d_inode);
+
+	pr_debug("ibmvmc: write: file = 0x%lx, count = 0x%lx\n",
+	     (unsigned long)file, (unsigned long)count);
+
+	send_msg(adapter, vmc_buffer, hmc, count);
+	ret = p - buffer;
+ out:
+	spin_unlock_irqrestore(&(hmc->lock), flags);
+	return (ssize_t)(ret);
+}
+
+static long ibmvmc_setup_hmc(struct ibmvmc_file_session *session)
+{
+	struct ibmvmc_hmc *hmc;
+	unsigned int valid, free, index;
+
+	if (ibmvmc.state == ibmvmc_state_failed) {
+		pr_warn("ibmvmc: Reserve HMC: state_failed\n");
+		return -EIO;
+	}
+
+	if (ibmvmc.state < ibmvmc_state_ready) {
+		pr_warn("ibmvmc: Reserve HMC: not state_ready\n");
+		return -EAGAIN;
+	}
+
+	/* Device is busy until capabilities have been exchanged and we
+	 * have a generic buffer for each possible HMC connection.
+	 */
+	for (index = 0; index <= ibmvmc.max_hmc_index; index++) {
+		valid = 0;
+		count_hmc_buffers(index, &valid, &free);
+		if (valid == 0) {
+			pr_warn("ibmvmc: buffers not ready for index %d\n",
+					index);
+			return -ENOBUFS;
+		}
+	}
+
+	/* Get an hmc object, and transition to ibmhmc_state_initial */
+	hmc = ibmvmc_get_free_hmc();
+	if (hmc == NULL) {
+		pr_warn("ibmvmc_setup_hmc: free hmc not found\n");
+		return -EBUSY;
+	}
+
+	hmc->session = hmc->session+1;
+	if (hmc->session == 0xff)
+		hmc->session = 1;
+
+	session->hmc = hmc;
+	hmc->adapter = &ibmvmc_adapter;
+	hmc->pSession = session;
+	session->valid = 1;
+
+	return 0;
+}
+
+static long ibmvmc_ioctl_sethmcid(struct ibmvmc_file_session *session,
+		unsigned char __user *new_hmc_id)
+{
+	struct ibmvmc_hmc *hmc;
+	struct ibmvmc_buffer *buffer;
+	size_t bytes;
+	char print_buffer[HMC_ID_LEN+1];
+	unsigned long flags;
+	long rc = 0;
+
+	/* Reserve HMC session */
+	hmc = session->hmc;
+	if (!hmc) {
+		rc = ibmvmc_setup_hmc(session);
+		if (rc)
+			return rc;
+
+		hmc = session->hmc;
+		if (!hmc) {
+			pr_err("ibmvmc: setup_hmc success but no hmc\n");
+			return -EIO;
+		}
+	}
+
+	if (hmc->state != ibmhmc_state_initial) {
+		pr_warn("ibmvmc: sethmcid: invalid state to send open 0x%x\n",
+				hmc->state);
+		return -EIO;
+	}
+
+	bytes = copy_from_user(hmc->hmc_id, new_hmc_id, HMC_ID_LEN);
+	if (bytes)
+		return -EFAULT;
+
+	/* Send Open Session command */
+	spin_lock_irqsave(&(hmc->lock), flags);
+	buffer = get_valid_hmc_buffer_locked(hmc->index);
+	spin_unlock_irqrestore(&(hmc->lock), flags);
+
+	if (buffer == NULL || (buffer->real_addr_local == NULL)) {
+		pr_warn("ibmvmc: sethmcid: no buffer available\n");
+		return -EIO;
+	}
+
+	/* Make sure buffer is NULL terminated before trying to print it */
+	memset(print_buffer, 0, HMC_ID_LEN+1);
+	strncpy(print_buffer, hmc->hmc_id, HMC_ID_LEN);
+	pr_info("ibmvmc: sethmcid: Set HMC ID: \"%s\"\n", print_buffer);
+
+	memcpy(buffer->real_addr_local, hmc->hmc_id, HMC_ID_LEN);
+	/* RDMA over ID, send open msg, change state to ibmhmc_state_opening */
+	rc = send_open(buffer, hmc);
+
+	return rc;
+}
+
+static long ibmvmc_ioctl_query(struct ibmvmc_file_session *session,
+		struct ibmvmc_query_struct __user *ret_struct)
+{
+	size_t bytes;
+	struct ibmvmc_query_struct query_struct;
+
+	memset(&query_struct, 0, sizeof(query_struct));
+	query_struct.have_vmc = (ibmvmc.state > ibmvmc_state_initial);
+	query_struct.state = ibmvmc.state;
+	query_struct.vmc_drc_index = ibmvmc.vmc_drc_index;
+
+	bytes = copy_to_user(ret_struct, &query_struct,
+			sizeof(query_struct));
+	if (bytes)
+		return -EFAULT;
+
+	return 0;
+}
+
+static long ibmvmc_ioctl_requestvmc(struct ibmvmc_file_session *session,
+		u32 __user *ret_vmc_index)
+{
+	/* TODO: (adreznec) Add locking to control multiple process access */
+	size_t bytes;
+	long rc;
+	u32 vmc_drc_index;
+
+	/* Call to request the VMC device from phyp*/
+	rc = h_request_vmc(&vmc_drc_index);
+	pr_debug("ibmvmc: requestvmc: H_REQUEST_VMC rc = 0x%lx\n", rc);
+
+	if (rc == H_SUCCESS) {
+		rc = 0;
+	} else if (rc == H_FUNCTION) {
+		pr_warn("ibmvmc: requestvmc: h_request_vmc not supported\n");
+		return -EPERM;
+	} else if (rc == H_AUTHORITY) {
+		pr_warn("ibmvmc: requestvmc: hypervisor denied vmc request\n");
+		return -EPERM;
+	} else if (rc == H_HARDWARE) {
+		pr_warn("ibmvmc: requestvmc: hypervisor hardware fault\n");
+		return -EIO;
+	} else if (rc == H_RESOURCE) {
+		pr_debug("ibmvmc: requestvmc: vmc resource unavailable\n");
+		return -EAGAIN;
+	} else if (rc == H_NOT_AVAILABLE) {
+		pr_warn("ibmvmc: requestvmc: system cannot be vmc managed\n");
+		return -EPERM;
+	} else if (rc == H_PARAMETER) {
+		return -EINVAL;
+	}
+
+	/* Success, set the vmc index in global struct */
+	ibmvmc.vmc_drc_index = vmc_drc_index;
+
+	bytes = copy_to_user(ret_vmc_index, &vmc_drc_index,
+			sizeof(*ret_vmc_index));
+	if (bytes) {
+		pr_warn("ibmvmc: requestvmc: copy to user failed.\n");
+		return -EFAULT;
+	}
+	return rc;
+}
+
+static long ibmvmc_ioctl(struct file *file,
+	  unsigned int cmd, unsigned long arg)
+{
+	struct ibmvmc_file_session *session = file->private_data;
+
+	pr_debug("ibmvmc: ioctl file=0x%lx, cmd=0x%x, arg=0x%lx, ses=0x%lx\n",
+			(unsigned long) file, cmd, arg,
+			(unsigned long) session);
+
+	if (!session) {
+		pr_warn("ibmvmc: ioctl: no session\n");
+		return -EIO;
+	}
+
+	switch (cmd) {
+	case VMC_IOCTL_SETHMCID:
+		return ibmvmc_ioctl_sethmcid(session,
+			(unsigned char __user *)arg);
+	case VMC_IOCTL_QUERY:
+		return ibmvmc_ioctl_query(session,
+			(struct ibmvmc_query_struct __user *)arg);
+	case VMC_IOCTL_REQUESTVMC:
+		return ibmvmc_ioctl_requestvmc(session,
+			(unsigned int __user *)arg);
+	default:
+		pr_warn("ibmvmc: unknown ioctl 0x%x\n", cmd);
+		return -EINVAL;
+	}
+}
+
+static const struct file_operations ibmvmc_fops = {
+	.owner		= THIS_MODULE,
+	.read		= ibmvmc_read,
+	.write		= ibmvmc_write,
+	.poll		= ibmvmc_poll,
+	.unlocked_ioctl	= ibmvmc_ioctl,
+	.open           = ibmvmc_open,
+	.release        = ibmvmc_close,
+};
+
+
+static int ibmvmc_add_buffer(struct crq_server_adapter *adapter,
+		struct crq_msg_ibmvmc *crq)
+{
+	int rc = 0;
+	u8 hmc_index, hmc_session;
+	u16 buffer_id;
+	struct ibmvmc_buffer *buffer;
+	unsigned long flags;
+
+	if (crq == NULL)
+		return -1;
+
+	hmc_session = crq->hmc_session;
+	hmc_index = crq->hmc_index;
+	buffer_id = be16_to_cpu(crq->var2.buffer_id);
+
+	if (hmc_index > ibmvmc.max_hmc_index) {
+		pr_err("ibmvmc: add_buffer: invalid hmc_index = 0x%x\n",
+				hmc_index);
+		ibmvmc_send_add_buffer_resp(adapter, VMC_MSG_INVALID_HMC_INDEX,
+					    hmc_session, hmc_index, buffer_id);
+		return -1;
+	}
+
+	if (buffer_id >= ibmvmc.max_buffer_pool_size) {
+		pr_err("ibmvmc: add_buffer: invalid buffer_id = 0x%x\n",
+				buffer_id);
+		ibmvmc_send_add_buffer_resp(adapter, VMC_MSG_INVALID_BUFFER_ID,
+					    hmc_session, hmc_index, buffer_id);
+		return -1;
+	}
+
+	spin_lock_irqsave(&(hmcs[hmc_index].lock), flags);
+	buffer = &(hmcs[hmc_index].buffer[buffer_id]);
+
+	if (buffer->real_addr_local || buffer->dma_addr_local) {
+		pr_warn("ibmvmc: add_buffer: already allocated id = 0x%lx\n",
+		     (unsigned long) buffer_id);
+		spin_unlock_irqrestore(&(hmcs[hmc_index].lock), flags);
+		ibmvmc_send_add_buffer_resp(adapter, VMC_MSG_INVALID_BUFFER_ID,
+					    hmc_session, hmc_index, buffer_id);
+		return -1;
+	}
+
+	buffer->real_addr_local = alloc_dma_buffer(to_vio_dev(adapter->dev),
+			ibmvmc.max_mtu,
+			&(buffer->dma_addr_local));
+
+	if (buffer->real_addr_local == NULL) {
+		pr_err("ibmvmc: add_buffer: alloc_dma_buffer failed.\n");
+		spin_unlock_irqrestore(&(hmcs[hmc_index].lock), flags);
+		ibmvmc_send_add_buffer_resp(adapter, VMC_MSG_INTERFACE_FAILURE,
+					    hmc_session, hmc_index, buffer_id);
+		return -1;
+	}
+
+	buffer->dma_addr_remote = be32_to_cpu(crq->var3.lioba);
+	buffer->size = ibmvmc.max_mtu;
+	buffer->owner = crq->var1.owner;
+	buffer->free = 1;
+	/* Must ensure valid==1 is observable only after all other fields are */
+	mb();
+	buffer->valid = 1;
+	buffer->id = buffer_id;
+
+	pr_debug("ibmvmc: add_buffer: successfully added a buffer:\n");
+	pr_debug("ibmvmc:    index: %d, session: %d, buffer: 0x%x, owner: %d\n",
+			hmc_index, hmc_session, buffer_id, buffer->owner);
+	pr_debug("ibmvmc:    local: 0x%x, remote: 0x%x\n",
+			(u32)buffer->dma_addr_local,
+			(u32)buffer->dma_addr_remote);
+	spin_unlock_irqrestore(&(hmcs[hmc_index].lock), flags);
+
+	ibmvmc_send_add_buffer_resp(adapter, VMC_MSG_SUCCESS, hmc_session,
+				    hmc_index, buffer_id);
+
+	return rc;
+}
+
+/*
+ * The hypervisor requested that we pick an unused buffer, and return it.
+ * Before sending the buffer back, we free any storage associated with the
+ * buffer.
+ */
+static int ibmvmc_rem_buffer(struct crq_server_adapter *adapter,
+		struct crq_msg_ibmvmc *crq)
+{
+	int rc = 0;
+	u8 hmc_index, hmc_session;
+	u16 buffer_id = 0;
+	struct ibmvmc_buffer *buffer;
+	unsigned long flags;
+
+	if (crq == NULL)
+		return -1;
+
+	hmc_session = crq->hmc_session;
+	hmc_index = crq->hmc_index;
+
+	if (hmc_index > ibmvmc.max_hmc_index) {
+		pr_warn("ibmvmc: rem_buffer: invalid hmc_index = 0x%x\n",
+				hmc_index);
+		ibmvmc_send_rem_buffer_resp(adapter, VMC_MSG_INVALID_HMC_INDEX,
+					    hmc_session, hmc_index, buffer_id);
+		return -1;
+	}
+
+	spin_lock_irqsave(&(hmcs[hmc_index].lock), flags);
+	buffer = get_free_hmc_buffer_locked(hmc_index);
+	if (buffer == NULL) {
+		pr_info("ibmvmc: rem_buffer: no buffer to remove\n");
+		spin_unlock_irqrestore(&(hmcs[hmc_index].lock), flags);
+		ibmvmc_send_rem_buffer_resp(adapter, VMC_MSG_NO_BUFFER,
+				hmc_session, hmc_index, VMC_INVALID_BUFFER_ID);
+		return -1;
+	}
+
+	buffer_id = buffer->id;
+
+	if (buffer->valid)
+		free_dma_buffer(to_vio_dev(adapter->dev),
+				    ibmvmc.max_mtu,
+				    buffer->real_addr_local,
+				    buffer->dma_addr_local);
+
+	memset(buffer, 0, sizeof(struct ibmvmc_buffer));
+	spin_unlock_irqrestore(&(hmcs[hmc_index].lock), flags);
+
+	pr_debug("ibmvmc: rem_buffer: removed buffer 0x%x.\n", buffer_id);
+	ibmvmc_send_rem_buffer_resp(adapter, VMC_MSG_SUCCESS, hmc_session,
+				    hmc_index, buffer_id);
+
+	return rc;
+}
+
+static int recv_msg(struct crq_server_adapter *adapter,
+		struct crq_msg_ibmvmc *crq)
+{
+	int rc = 0;
+	u8 hmc_index, hmc_session;
+	u16 buffer_id;
+	struct ibmvmc_buffer *buffer;
+	unsigned long msg_len;
+	struct ibmvmc_hmc *hmc;
+	unsigned long flags;
+
+	if (crq == NULL)
+		return -1;
+
+	/* Hypervisor writes CRQs directly into our memory in big endian */
+	pr_debug("ibmvmc: recv_msg: msg from HV 0x%016llx 0x%016llx\n",
+			be64_to_cpu(*((unsigned long *)crq)),
+			be64_to_cpu(*(((unsigned long *)crq)+1)));
+
+	hmc_session = crq->hmc_session;
+	hmc_index = crq->hmc_index;
+	buffer_id = be16_to_cpu(crq->var2.buffer_id);
+	msg_len = be32_to_cpu(crq->var3.msg_len);
+
+	if (hmc_index > ibmvmc.max_hmc_index) {
+		pr_err("ibmvmc: recv_msg: invalid hmc_index = 0x%x\n",
+				hmc_index);
+		ibmvmc_send_add_buffer_resp(adapter, VMC_MSG_INVALID_HMC_INDEX,
+					    hmc_session, hmc_index, buffer_id);
+		return -1;
+	}
+
+	if (buffer_id >= ibmvmc.max_buffer_pool_size) {
+		pr_err("ibmvmc: recv_msg: invalid buffer_id = 0x%x\n",
+				buffer_id);
+		ibmvmc_send_add_buffer_resp(adapter, VMC_MSG_INVALID_BUFFER_ID,
+					    hmc_session, hmc_index, buffer_id);
+		return -1;
+	}
+
+	hmc = &hmcs[hmc_index];
+	spin_lock_irqsave(&(hmc->lock), flags);
+
+	if (hmc->state == ibmhmc_state_free) {
+		pr_err("ibmvmc: recv_msg: invalid hmc state = 0x%x\n",
+				hmc->state);
+		/* HMC connection is not valid (possibly was reset under us). */
+		spin_unlock_irqrestore(&(hmc->lock), flags);
+		return -1;
+	}
+
+	buffer = &(hmc->buffer[buffer_id]);
+
+	if ((buffer->valid == 0) || buffer->owner == VMC_BUF_OWNER_ALPHA) {
+		pr_err("ibmvmc: recv_msg: not valid, or not HV.  0x%x 0x%x\n",
+		    buffer->valid, buffer->owner);
+		spin_unlock_irqrestore(&(hmc->lock), flags);
+		return -1;
+	}
+
+	/* RDMA the data into the partition. */
+	rc = h_copy_rdma(msg_len,
+			 adapter->riobn,
+			 buffer->dma_addr_remote,
+			 adapter->liobn,
+			 buffer->dma_addr_local);
+
+	pr_debug("ibmvmc: recv_msg: msg_len = 0x%x, buffer_id = 0x%x, queue_head = 0x%x, hmc_idx = 0x%x\n",
+			(unsigned int)msg_len, (unsigned int)buffer_id,
+			(unsigned int)hmc->queue_head, (unsigned int)hmc_index);
+	buffer->msg_len = msg_len;
+	buffer->free = 0;
+	buffer->owner = VMC_BUF_OWNER_ALPHA;
+
+	if (rc) {
+		pr_err("ibmvmc: failure in recv_msg: h_copy_rdma = 0x%x\n", rc);
+		spin_unlock_irqrestore(&(hmc->lock), flags);
+		return -1;
+	}
+
+	/* Must be locked because read operates on the same data */
+	hmc->queue_outbound_msgs[hmc->queue_head] = buffer_id;
+	hmc->queue_head++;
+	if (hmc->queue_head == ibmvmc_max_buf_pool_size)
+		hmc->queue_head = 0;
+
+	if (hmc->queue_head == hmc->queue_tail)
+		pr_err("recv_msg: outbound buffer queue wrapped.\n");
+
+	spin_unlock_irqrestore(&(hmc->lock), flags);
+
+	wake_up_interruptible(&ibmvmc_read_wait);
+
+	return 0;
+}
+
+static void ibmvmc_process_capabilities(struct crq_msg_ibmvmc *crqp)
+{
+	struct crq_msg_ibmvmc_admin *crq = (struct crq_msg_ibmvmc_admin *)crqp;
+
+	if ((be16_to_cpu(crq->version) >> 8) !=
+			(IBMVMC_PROTOCOL_VERSION >> 8)) {
+		pr_err("ibmvmc: init failed, incompatible versions 0x%x 0x%x\n",
+				be16_to_cpu(crq->version),
+				IBMVMC_PROTOCOL_VERSION);
+		ibmvmc.state = ibmvmc_state_failed;
+		return;
+	}
+
+	ibmvmc.max_mtu = min_t(u32, ibmvmc_max_mtu, be32_to_cpu(crq->max_mtu));
+	ibmvmc.max_buffer_pool_size = min_t(u16, ibmvmc_max_buf_pool_size,
+			be16_to_cpu(crq->pool_size));
+	ibmvmc.max_hmc_index = min_t(u8, ibmvmc_max_hmcs, crq->max_hmc) - 1;
+	ibmvmc.state = ibmvmc_state_ready;
+
+	pr_info("ibmvmc: capabilities: mtu=0x%x, pool_size=0x%x, max_hmc=0x%x\n",
+			ibmvmc.max_mtu, ibmvmc.max_buffer_pool_size,
+			ibmvmc.max_hmc_index);
+}
+
+static int ibmvmc_validate_hmc_session(struct crq_msg_ibmvmc *crq)
+{
+	unsigned char hmc_index;
+
+	hmc_index = crq->hmc_index;
+
+	if (crq->hmc_session == 0)
+		return 0;
+
+	if (hmc_index > ibmvmc.max_hmc_index)
+		return -1;
+
+	if (hmcs[hmc_index].session != crq->hmc_session) {
+		pr_warn("ibmvmc: drop, bad session: expected 0x%x, recv 0x%x\n",
+				hmcs[hmc_index].session, crq->hmc_session);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void ibmvmc_reset(bool bReleaseReaders)
+{
+	int i;
+
+	pr_info("ibmvmc: *** Reset to initial state.\n");
+	for (i = 0; i < ibmvmc_max_hmcs; i++)
+		if (hmcs[i].state != ibmhmc_state_free)
+			ibmvmc_return_hmc(&hmcs[i], bReleaseReaders);
+
+	ibmvmc.state = ibmvmc_state_crqinit;
+}
+
+static void ibmvmc_process_open_resp(struct crq_msg_ibmvmc *crq)
+{
+	unsigned char hmc_index;
+	unsigned short buffer_id;
+
+	hmc_index = crq->hmc_index;
+	if (hmc_index > ibmvmc.max_hmc_index)
+		/* Why would PHYP give an index > max negotiated? */
+		ibmvmc_reset(false);
+
+	if (crq->status) {
+		pr_warn("ibmvmc: open_resp: failed - status 0x%x\n",
+				crq->status);
+		ibmvmc_return_hmc(&hmcs[hmc_index], false);
+		return;
+	}
+
+	if (hmcs[hmc_index].state == ibmhmc_state_opening) {
+		buffer_id = be16_to_cpu(crq->var2.buffer_id);
+		if (buffer_id >= ibmvmc.max_buffer_pool_size) {
+			pr_err("ibmvmc: open_resp: invalid buffer_id = 0x%x\n",
+					buffer_id);
+			hmcs[hmc_index].state = ibmhmc_state_failed;
+		} else {
+			return_hmc_buffer(&(hmcs[hmc_index]),
+					&(hmcs[hmc_index].buffer[buffer_id]));
+			hmcs[hmc_index].state = ibmhmc_state_ready;
+			pr_debug("ibmvmc: open_resp: set hmc state = ready\n");
+		}
+	} else
+		pr_warn("ibmvmc: open_resp: invalid hmc state (0x%x)\n",
+				hmcs[hmc_index].state);
+}
+
+/*
+ * If the close fails, simply reset the entire driver as the state of the VMC
+ * must be in tough shape.
+ */
+static void ibmvmc_process_close_resp(struct crq_msg_ibmvmc *crq)
+{
+	unsigned char hmc_index;
+
+	hmc_index = crq->hmc_index;
+	if (hmc_index > ibmvmc.max_hmc_index) {
+		ibmvmc_reset(false);
+		return;
+	}
+
+	if (crq->status) {
+		pr_warn("ibmvmc: close_resp: failed - status 0x%x\n",
+				crq->status);
+		ibmvmc_reset(false);
+		return;
+	}
+
+	ibmvmc_return_hmc(&hmcs[hmc_index], false);
+}
+
+static void ibmvmc_crq_process(struct crq_server_adapter *adapter,
+		struct crq_msg_ibmvmc *crq)
+{
+	switch (crq->type) {
+	case VMC_MSG_CAP_RESP:
+		pr_debug("ibmvmc: CRQ recv: capabilities resp (0x%x)\n",
+				crq->type);
+		if (ibmvmc.state == ibmvmc_state_capabilities)
+			ibmvmc_process_capabilities(crq);
+		else
+			pr_warn("ibmvmc: caps msg invalid in state 0x%x\n",
+					ibmvmc.state);
+		break;
+	case VMC_MSG_OPEN_RESP:
+		pr_debug("ibmvmc: CRQ recv: open resp (0x%x)\n", crq->type);
+		if (ibmvmc_validate_hmc_session(crq) == 0)
+			ibmvmc_process_open_resp(crq);
+		break;
+	case VMC_MSG_ADD_BUF:
+		pr_debug("ibmvmc: CRQ recv: add buf (0x%x)\n", crq->type);
+		if (ibmvmc_validate_hmc_session(crq) == 0)
+			ibmvmc_add_buffer(adapter, crq);
+		break;
+	case VMC_MSG_REM_BUF:
+		pr_debug("ibmvmc: CRQ recv: rem buf (0x%x)\n", crq->type);
+		if (ibmvmc_validate_hmc_session(crq) == 0)
+			ibmvmc_rem_buffer(adapter, crq);
+		break;
+	case VMC_MSG_SIGNAL:
+		pr_debug("ibmvmc: CRQ recv: signal msg (0x%x)\n", crq->type);
+		if (ibmvmc_validate_hmc_session(crq) == 0)
+			recv_msg(adapter, crq);
+		break;
+	case VMC_MSG_CLOSE_RESP:
+		pr_debug("ibmvmc: CRQ recv: close resp (0x%x)\n", crq->type);
+		if (ibmvmc_validate_hmc_session(crq) == 0)
+			ibmvmc_process_close_resp(crq);
+		break;
+	case VMC_MSG_CAP:
+	case VMC_MSG_OPEN:
+	case VMC_MSG_CLOSE:
+	case VMC_MSG_ADD_BUF_RESP:
+	case VMC_MSG_REM_BUF_RESP:
+		pr_warn("ibmvmc: CRQ recv: unexpected msg (0x%x)\n", crq->type);
+		break;
+	default:
+		pr_warn("ibmvmc: CRQ recv: unknown msg (0x%x)\n", crq->type);
+		break;
+	}
+}
+
+static void ibmvmc_handle_crq_init(struct crq_msg_ibmvmc *crq,
+		struct crq_server_adapter *adapter)
+{
+	switch (crq->type) {
+	case 0x01:	/* Initialization message */
+		pr_debug("ibmvmc: CRQ recv: CRQ init msg - state 0x%x\n",
+				ibmvmc.state);
+		if (ibmvmc.state == ibmvmc_state_crqinit) {
+			/* Send back a response */
+			if (ibmvmc_send_crq(adapter, 0xC002000000000000,
+				0) == 0)
+				ibmvmc_send_capabilities(adapter);
+			else
+				pr_err("ibmvmc: Unable to send init rsp\n");
+		} else
+			pr_err("ibmvmc: Invalid state 0x%x mtu = 0x%x\n",
+					ibmvmc.state, ibmvmc.max_mtu);
+
+		break;
+	case 0x02:	/* Initialization response */
+		pr_debug("ibmvmc: CRQ recv: initialization resp msg - state 0x%x\n",
+				ibmvmc.state);
+		if (ibmvmc.state == ibmvmc_state_crqinit)
+			ibmvmc_send_capabilities(adapter);
+		break;
+	default:
+		pr_warn("ibmvmc: Unknown crq message type 0x%lx\n",
+				(unsigned long) crq->type);
+	}
+}
+
+static void ibmvmc_handle_crq(struct crq_msg_ibmvmc *crq,
+		struct crq_server_adapter *adapter)
+{
+	switch (crq->valid) {
+	case 0xC0:		/* initialization */
+		ibmvmc_handle_crq_init(crq, adapter);
+		break;
+	case 0xFF:	/* Hypervisor telling us the connection is closed */
+		pr_warn("ibmvmc: CRQ recv: virtual adapter failed - resetting.\n");
+		ibmvmc_reset(true);
+		break;
+	case 0x80:	/* real payload */
+		ibmvmc_crq_process(adapter, crq);
+		break;
+	default:
+		pr_warn("ibmvmc: CRQ recv: unknown msg 0x%02x.\n", crq->valid);
+		break;
+	}
+}
+
+static void ibmvmc_task(struct work_struct *work)
+{
+	struct crq_server_adapter *adapter =
+		container_of(work, struct crq_server_adapter, work);
+	struct vio_dev *vdev = to_vio_dev(adapter->dev);
+	struct crq_msg_ibmvmc *crq;
+	int done = 0;
+
+	while (!done) {
+		/* Pull all the valid messages off the CRQ */
+		while ((crq = crq_queue_next_crq(&adapter->queue)) != NULL) {
+			ibmvmc_handle_crq(crq, adapter);
+			crq->valid = 0x00;
+		}
+
+		vio_enable_interrupts(vdev);
+		crq = crq_queue_next_crq(&adapter->queue);
+		if (crq != NULL) {
+			vio_disable_interrupts(vdev);
+			ibmvmc_handle_crq(crq, adapter);
+			crq->valid = 0x00;
+		} else
+			done = 1;
+	}
+}
+
+static int ibmvmc_init_crq_queue(struct crq_server_adapter *adapter)
+{
+	int rc;
+	int retrc;
+	struct vio_dev *vdev = to_vio_dev(adapter->dev);
+	struct crq_queue *queue = &adapter->queue;
+
+	queue->msgs = (struct crq_msg_ibmvmc *)get_zeroed_page(GFP_KERNEL);
+
+	if (!queue->msgs)
+		goto malloc_failed;
+
+	queue->size = PAGE_SIZE / sizeof(*queue->msgs);
+
+	queue->msg_token = dma_map_single(adapter->dev, queue->msgs,
+					    queue->size * sizeof(*queue->msgs),
+					    DMA_BIDIRECTIONAL);
+
+	if (dma_mapping_error(adapter->dev, queue->msg_token))
+		goto map_failed;
+
+	retrc = rc = plpar_hcall_norets(H_REG_CRQ,
+					vdev->unit_address,
+					queue->msg_token, PAGE_SIZE);
+	if (rc == H_RESOURCE)
+		rc = ibmvmc_reset_crq_queue(adapter);
+
+	if (rc == 2) {
+		pr_warn("ibmvmc: Partner adapter not ready\n");
+		retrc = 0;
+	} else if (rc != 0) {
+		pr_err("ibmvmc: Error %d opening adapter\n", rc);
+		goto reg_crq_failed;
+	}
+
+	queue->cur = 0;
+	spin_lock_init(&queue->lock);
+
+	adapter->work_queue = create_singlethread_workqueue(ibmvmc_workq_name);
+	if (adapter->work_queue == NULL) {
+		pr_err("ibmvmc: couldn't allocate work queue\n");
+		goto create_workqueue_failed;
+	}
+
+	INIT_WORK(&adapter->work, ibmvmc_task);
+
+	if (request_irq(vdev->irq,
+			ibmvmc_handle_event,
+			0, "ibmvmc", (void *)adapter) != 0) {
+		pr_err("ibmvmc: couldn't register irq 0x%x\n",
+				vdev->irq);
+		goto req_irq_failed;
+	}
+
+	rc = vio_enable_interrupts(vdev);
+	if (rc != 0) {
+		pr_err("ibmvmc: Error %d enabling interrupts!!!\n", rc);
+		goto req_irq_failed;
+	}
+
+	return retrc;
+
+req_irq_failed:
+	/* Cannot have any work since we either never got our IRQ registered,
+	 * or never got interrupts enabled
+	 */
+	destroy_workqueue(adapter->work_queue);
+create_workqueue_failed:
+	h_free_crq(vdev->unit_address);
+reg_crq_failed:
+	dma_unmap_single(adapter->dev,
+			queue->msg_token,
+			queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL);
+map_failed:
+	free_page((unsigned long)queue->msgs);
+malloc_failed:
+	return -1;
+}
+
+/* Fill in the liobn and riobn fields on the adapter */
+static int read_dma_window(struct vio_dev *vdev,
+				struct crq_server_adapter *adapter)
+{
+	const __be32 *dma_window;
+	const __be32 *prop;
+
+	/* TODO Using of_parse_dma_window would be better, but it doesn't give
+	 * a way to read multiple windows without already knowing the size of
+	 * a window or the number of windows
+	 */
+	dma_window =
+		(const __be32 *)vio_get_attribute(vdev, "ibm,my-dma-window",
+						NULL);
+	if (!dma_window) {
+		pr_err("ibmvmc: Couldn't find ibm,my-dma-window property\n");
+		return -1;
+	}
+
+	adapter->liobn = be32_to_cpu(*dma_window);
+	dma_window++;
+
+	prop = (const __be32 *)vio_get_attribute(vdev, "ibm,#dma-address-cells",
+						NULL);
+	if (!prop) {
+		pr_warn("ibmvmc: Couldn't find ibm,#dma-address-cells property\n");
+		dma_window++;
+	} else
+		dma_window += be32_to_cpu(*prop);
+
+	prop = (const __be32 *)vio_get_attribute(vdev, "ibm,#dma-size-cells",
+						NULL);
+	if (!prop) {
+		pr_warn("ibmvmc: Couldn't find ibm,#dma-size-cells property\n");
+		dma_window++;
+	} else
+		dma_window += be32_to_cpu(*prop);
+
+	/* dma_window should point to the second window now */
+	adapter->riobn = be32_to_cpu(*dma_window);
+
+	return 0;
+}
+
+static int ibmvmc_probe(struct vio_dev *vdev, const struct vio_device_id *id)
+{
+	struct crq_server_adapter *adapter = &ibmvmc_adapter;
+	int rc;
+
+	pr_info("ibmvmc: Probe for UA 0x%x\n", vdev->unit_address);
+
+	dev_set_drvdata(&vdev->dev, NULL);
+	memset(adapter, 0, sizeof(*adapter));
+	adapter->dev = &vdev->dev;
+	sprintf(adapter->name, "%s:%x", ibmvmc_driver_name, vdev->unit_address);
+
+	rc = read_dma_window(vdev, adapter);
+	if (rc != 0) {
+		ibmvmc.state = ibmvmc_state_failed;
+		return -1;
+	}
+
+	pr_debug("ibmvmc: Probe: liobn 0x%x, riobn 0x%x\n", adapter->liobn,
+			adapter->riobn);
+
+	rc = ibmvmc_init_crq_queue(adapter);
+	if (rc != 0 && rc != H_RESOURCE) {
+		pr_err("ibmvmc: Error initializing CRQ.  rc = 0x%x\n", rc);
+		ibmvmc.state = ibmvmc_state_failed;
+		return -1;
+	}
+
+	ibmvmc.state = ibmvmc_state_crqinit;
+
+	/* Try to send an initialization message.  Note that this is allowed
+	 * to fail if the other end is not acive.  In that case we just wait
+	 * for the other side to initialize.
+	 */
+	if (ibmvmc_send_crq(adapter, 0xC001000000000000LL, 0) != 0
+			&& rc != H_RESOURCE)
+		pr_warn("ibmvmc: Failed to send initialize CRQ message\n");
+
+	dev_set_drvdata(&vdev->dev, adapter);
+
+	return 0;
+}
+
+static int ibmvmc_remove(struct vio_dev *vdev)
+{
+	struct crq_server_adapter *adapter = dev_get_drvdata(&vdev->dev);
+
+	pr_info("ibmvmc: entering remove for UA 0x%x\n", vdev->unit_address);
+	ibmvmc_release_crq_queue(adapter);
+
+	return 0;
+}
+
+static struct vio_device_id ibmvmc_device_table[] = {
+	{ "ibm,vmc", "IBM,vmc" },
+	{ "", "" }
+};
+
+MODULE_DEVICE_TABLE(vio, ibmvmc_device_table);
+
+static struct vio_driver ibmvmc_driver = {
+	.name        = ibmvmc_driver_name,
+	.id_table    = ibmvmc_device_table,
+	.probe       = ibmvmc_probe,
+	.remove      = ibmvmc_remove,
+};
+
+static void __init ibmvmc_scrub_module_parms(void)
+{
+	if (ibmvmc_max_mtu > MAX_MTU) {
+		pr_warn("ibmvmc: Max MTU reduced to %d\n", MAX_MTU);
+		ibmvmc_max_mtu = MAX_MTU;
+	} else if (ibmvmc_max_mtu < MIN_MTU) {
+		pr_warn("ibmvmc: Max MTU increased to %d\n", MIN_MTU);
+		ibmvmc_max_mtu = MIN_MTU;
+	}
+
+	if (ibmvmc_max_buf_pool_size > MAX_BUF_POOL_SIZE) {
+		pr_warn("ibmvmc: Max buffer pool size reduced to %d\n",
+				MAX_BUF_POOL_SIZE);
+		ibmvmc_max_buf_pool_size = MAX_BUF_POOL_SIZE;
+	} else if (ibmvmc_max_buf_pool_size < MIN_BUF_POOL_SIZE) {
+		pr_warn("ibmvmc: Max buffer pool size increased to %d\n",
+				MIN_BUF_POOL_SIZE);
+		ibmvmc_max_buf_pool_size = MIN_BUF_POOL_SIZE;
+	}
+
+	if (ibmvmc_max_hmcs > MAX_HMCS) {
+		pr_warn("ibmvmc: Max HMCs reduced to %d\n", MAX_HMCS);
+		ibmvmc_max_hmcs = MAX_HMCS;
+	} else if (ibmvmc_max_hmcs < MIN_HMCS) {
+		pr_warn("ibmvmc: Max HMCs increased to %d\n", MIN_HMCS);
+		ibmvmc_max_hmcs = MIN_HMCS;
+	}
+}
+
+static int __init ibmvmc_module_init(void)
+{
+	int rc, i, j;
+
+	ibmvmc.state = ibmvmc_state_initial;
+	pr_info("ibmvmc: version %s\n", IBMVMC_DRIVER_VERSION);
+
+	/* Dynamically allocate ibmvmc major number */
+	if (alloc_chrdev_region(&ibmvmc_chrdev, 0, VMC_NUM_MINORS,
+		ibmvmc_driver_name)) {
+		pr_err("ibmvmc: unable to allocate a dev_t\n");
+		rc = -EIO;
+		goto alloc_chrdev_failed;
+	}
+	pr_info("ibmvmc: node %d:%d\n", MAJOR(ibmvmc_chrdev),
+			MINOR(ibmvmc_chrdev));
+
+	/* Initialize data structures */
+	memset(hmcs, 0, sizeof(struct ibmvmc_hmc) * MAX_HMCS);
+	for (i = 0; i < MAX_HMCS; i++) {
+		spin_lock_init(&hmcs[i].lock);
+		hmcs[i].state = ibmhmc_state_free;
+		for (j = 0; j < MAX_BUF_POOL_SIZE; j++)
+			hmcs[i].queue_outbound_msgs[j] = VMC_INVALID_BUFFER_ID;
+	}
+
+	/* Sanity check module parms */
+	ibmvmc_scrub_module_parms();
+
+	/*
+	 * Initialize some reasonable values.  Might be negotiated smaller
+	 * values during the capabilities exchange.
+	 */
+	ibmvmc.max_mtu = ibmvmc_max_mtu;
+	ibmvmc.max_buffer_pool_size = ibmvmc_max_buf_pool_size;
+	ibmvmc.max_hmc_index = ibmvmc_max_hmcs - 1;
+
+	/* Once cdev_add is complete, apps can start trying to use the vmc.
+	 * They will get EBUSY on open until after the probe has completed.
+	 */
+	cdev_init(&ibmvmc.cdev, &ibmvmc_fops);
+	ibmvmc.cdev.owner = THIS_MODULE;
+	ibmvmc.cdev.ops = &ibmvmc_fops;
+	rc = cdev_add(&ibmvmc.cdev, ibmvmc_chrdev, VMC_NUM_MINORS);
+	if (rc) {
+		pr_err("ibmvmc: unable to add cdev: %d\n", rc);
+		goto cdev_add_failed;
+	}
+
+	ibmvmc_class = class_create(THIS_MODULE, "ibmvmc");
+	if (IS_ERR(ibmvmc_class)) {
+		rc = PTR_ERR(ibmvmc_class);
+		pr_err("ibmvmc: class regiter ibmvmc failed");
+		goto cdev_add_failed;
+	}
+
+	ibmvmc_dev = device_create(ibmvmc_class, NULL, ibmvmc_chrdev,
+			NULL, "ibmvmc");
+	if (IS_ERR(ibmvmc_dev)) {
+		rc = PTR_ERR(ibmvmc_dev);
+		pr_err("ibmvmc: device add failed");
+		goto device_create_failed;
+	}
+	rc = vio_register_driver(&ibmvmc_driver);
+
+	if (rc) {
+		pr_err("ibmvmc: rc %d from vio_register_driver\n", rc);
+		goto vio_reg_failed;
+	}
+
+	return 0;
+
+vio_reg_failed:
+	cdev_del(&ibmvmc.cdev);
+device_create_failed:
+	class_destroy(ibmvmc_class);
+cdev_add_failed:
+	unregister_chrdev_region(ibmvmc_chrdev, VMC_NUM_MINORS);
+alloc_chrdev_failed:
+	return rc;
+}
+
+static void __exit ibmvmc_module_exit(void)
+{
+	pr_info("ibmvmc_module_exit\n");
+	vio_unregister_driver(&ibmvmc_driver);
+	cdev_del(&ibmvmc.cdev);
+	device_destroy(ibmvmc_class, ibmvmc_chrdev);
+	class_destroy(ibmvmc_class);
+	unregister_chrdev_region(ibmvmc_chrdev, VMC_NUM_MINORS);
+}
+
+module_init(ibmvmc_module_init);
+module_exit(ibmvmc_module_exit);
diff --git a/drivers/misc/ibmvmc.h b/drivers/misc/ibmvmc.h
new file mode 100644
index 0000000..4b11c78
--- /dev/null
+++ b/drivers/misc/ibmvmc.h
@@ -0,0 +1,203 @@
+/*
+ * IBM Power Systems Virtual Management Channel Support.
+ *
+ * Copyright (c) 2004, 2016 IBM Corp.
+ *   Dave Engebretsen engebret@...ibm.com
+ *   Steven Royer seroyer@...ux.vnet.ibm.com
+ *   Adam Reznechek adreznec@...ux.vnet.ibm.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ */
+#ifndef IBMVMC_H
+#define IBMVMC_H
+
+#include <linux/types.h>
+#include <linux/workqueue.h>
+#include <linux/cdev.h>
+
+#include <asm/vio.h>
+
+#define IBMVMC_PROTOCOL_VERSION    0x0101
+
+#define LOG_LEVEL_MIN     1
+#define LOG_LEVEL_NORM    3
+#define LOG_LEVEL_TRACE   7
+#define LOG_LEVEL_MAX     10
+#define DEFAULT_LOG_LEVEL LOG_LEVEL_NORM
+
+#define MIN_BUF_POOL_SIZE 16
+#define MIN_HMCS          1
+#define MIN_MTU           4096
+#define MAX_BUF_POOL_SIZE 64
+#define MAX_HMCS          2
+#define MAX_MTU           (4*4096)
+#define DEFAULT_BUF_POOL_SIZE 32
+#define DEFAULT_HMCS          1
+#define DEFAULT_MTU           4096
+#define HMC_ID_LEN        32
+
+#define VMC_INVALID_BUFFER_ID 0xFFFF
+
+#define VMC_NUM_MINORS	1
+
+/* ioctl numbers */
+#define VMC_BASE	     0xCC
+#define VMC_IOCTL_SETHMCID   _IOW(VMC_BASE, 0x00, unsigned char *)
+#define VMC_IOCTL_QUERY      _IOR(VMC_BASE, 0x01, struct ibmvmc_query_struct)
+#define VMC_IOCTL_REQUESTVMC _IOR(VMC_BASE, 0x02, u32)
+
+#define VMC_MSG_CAP          0x01
+#define VMC_MSG_CAP_RESP     0x81
+#define VMC_MSG_OPEN         0x02
+#define VMC_MSG_OPEN_RESP    0x82
+#define VMC_MSG_CLOSE        0x03
+#define VMC_MSG_CLOSE_RESP   0x83
+#define VMC_MSG_ADD_BUF      0x04
+#define VMC_MSG_ADD_BUF_RESP 0x84
+#define VMC_MSG_REM_BUF      0x05
+#define VMC_MSG_REM_BUF_RESP 0x85
+#define VMC_MSG_SIGNAL       0x06
+
+#define VMC_MSG_SUCCESS 0
+#define VMC_MSG_INVALID_HMC_INDEX 1
+#define VMC_MSG_INVALID_BUFFER_ID 2
+#define VMC_MSG_CLOSED_HMC        3
+#define VMC_MSG_INTERFACE_FAILURE 4
+#define VMC_MSG_NO_BUFFER         5
+
+#define VMC_BUF_OWNER_ALPHA 0
+#define VMC_BUF_OWNER_HV    1
+
+enum ibmvmc_states {
+	ibmvmc_state_initial      = 0,
+	ibmvmc_state_crqinit      = 1,
+	ibmvmc_state_capabilities = 2,
+	ibmvmc_state_ready        = 3,
+	ibmvmc_state_failed       = 4,
+};
+
+enum ibmhmc_states {
+	/* HMC connection not established */
+	ibmhmc_state_free    = 0,
+
+	/* HMC connection established (open called) */
+	ibmhmc_state_initial = 1,
+
+	/* open msg sent to HV, due to ioctl(1) call */
+	ibmhmc_state_opening = 2,
+
+	/* HMC connection ready, open resp msg from HV */
+	ibmhmc_state_ready   = 3,
+
+	/* HMC connection failure */
+	ibmhmc_state_failed  = 4,
+};
+
+struct ibmvmc_buffer {
+	u8 valid;    /* 1 when DMA storage allocated to buffer          */
+	u8 free;     /* 1 when buffer available for the Alpha Partition */
+	u8 owner;
+	u16 id;
+	u32 size;
+	u32 msg_len;
+	dma_addr_t dma_addr_local;
+	dma_addr_t dma_addr_remote;
+	void *real_addr_local;
+};
+
+struct crq_msg_ibmvmc_admin {
+	u8 valid;     /* RPA Defined           */
+	u8 type;      /* ibmvmc msg type       */
+	u8 status;    /* Response msg status   */
+	u8 rsvd[2];
+	u8 max_hmc;
+	__be16 pool_size;
+	__be32 max_mtu;
+	__be16 crq_size;
+	__be16 version;
+};
+
+struct crq_msg_ibmvmc {
+	u8 valid;     /* RPA Defined           */
+	u8 type;      /* ibmvmc msg type       */
+	u8 status;    /* Response msg status   */
+	union {
+		u8 rsvd;  /* Reserved              */
+		u8 owner;
+	} var1;
+	u8 hmc_session;
+	u8 hmc_index;
+	union {
+		__be16 rsvd;
+		__be16 buffer_id;
+	} var2;
+	__be32 rsvd;
+	union {
+		__be32 rsvd;
+		__be32 lioba;
+		__be32 msg_len;
+	} var3;
+};
+
+/* an RPA command/response transport queue */
+struct crq_queue {
+	struct crq_msg_ibmvmc *msgs;
+	int size, cur;
+	dma_addr_t msg_token;
+	spinlock_t lock;
+};
+
+/* VMC server adapter settings */
+struct crq_server_adapter {
+	char name[16];
+	struct device *dev;
+	struct crq_queue queue;
+	u32 liobn;
+	u32 riobn;
+	struct work_struct work;
+	struct workqueue_struct *work_queue;
+};
+
+/* Driver wide settings */
+struct ibmvmc_struct {
+	u32 state;
+	u32 max_mtu;
+	u32 max_buffer_pool_size;
+	u32 max_hmc_index;
+	struct crq_server_adapter *adapter;
+	struct cdev cdev;
+	u32 vmc_drc_index;
+};
+
+struct ibmvmc_file_session;
+
+/* Connection specific settings */
+struct ibmvmc_hmc {
+	u8 session;
+	u8 index;
+	u32 state;
+	struct crq_server_adapter *adapter;
+	spinlock_t lock;
+	unsigned char hmc_id[HMC_ID_LEN];
+	struct ibmvmc_buffer buffer[MAX_BUF_POOL_SIZE];
+	unsigned short queue_outbound_msgs[MAX_BUF_POOL_SIZE];
+	int queue_head, queue_tail;
+	struct ibmvmc_file_session *pSession;
+};
+
+struct ibmvmc_file_session {
+	struct file *file;
+	struct ibmvmc_hmc *hmc;
+	bool valid;
+};
+
+struct ibmvmc_query_struct {
+	int have_vmc;
+	int state;
+	int vmc_drc_index;
+};
+
+#endif
-- 
1.9.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ