aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/misc/mic/host/mic_virtio.c
blob: 5b8494bd1e003ff9cb53d49fa7ffd22798893912 (plain) (tree)
























                                                                          
                              










































































































































































                                                                            
                                                         









                                                                 
                                                         

















                                                                               
                                                                       











































































































































                                                                          

























                                                                  
                                                          










































                                                                         

                                                                 






























































                                                                             
                                                              







                                     
                       





                                                 
                                          

                                      
                    




























                                                                             
                                                             











                                                                           
                                                                              






                                                                       
                                                                        

















                                                                          

                                                                     
































                                                                             
                                                 
                                                        
                                                      












































                                                                        
                                                 
                                                        
                                                      





















                                                                            
/*
 * Intel MIC Platform Software Stack (MPSS)
 *
 * Copyright(c) 2013 Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * The full GNU General Public License is included in this distribution in
 * the file called "COPYING".
 *
 * Intel MIC Host driver.
 *
 */
#include <linux/pci.h>
#include <linux/sched.h>
#include <linux/uaccess.h>

#include <linux/mic_common.h>
#include "../common/mic_dev.h"
#include "mic_device.h"
#include "mic_smpt.h"
#include "mic_virtio.h"

/*
 * Initiates the copies across the PCIe bus from card memory to
 * a user space buffer.
 */
static int mic_virtio_copy_to_user(struct mic_vdev *mvdev,
		void __user *ubuf, size_t len, u64 addr)
{
	int err;
	void __iomem *dbuf = mvdev->mdev->aper.va + addr;
	/*
	 * We are copying from IO below an should ideally use something
	 * like copy_to_user_fromio(..) if it existed.
	 */
	if (copy_to_user(ubuf, dbuf, len)) {
		err = -EFAULT;
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, err);
		goto err;
	}
	mvdev->in_bytes += len;
	err = 0;
err:
	return err;
}

/*
 * Initiates copies across the PCIe bus from a user space
 * buffer to card memory.
 */
static int mic_virtio_copy_from_user(struct mic_vdev *mvdev,
		void __user *ubuf, size_t len, u64 addr)
{
	int err;
	void __iomem *dbuf = mvdev->mdev->aper.va + addr;
	/*
	 * We are copying to IO below and should ideally use something
	 * like copy_from_user_toio(..) if it existed.
	 */
	if (copy_from_user(dbuf, ubuf, len)) {
		err = -EFAULT;
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, err);
		goto err;
	}
	mvdev->out_bytes += len;
	err = 0;
err:
	return err;
}

#define MIC_VRINGH_READ true

/* The function to call to notify the card about added buffers */
static void mic_notify(struct vringh *vrh)
{
	struct mic_vringh *mvrh = container_of(vrh, struct mic_vringh, vrh);
	struct mic_vdev *mvdev = mvrh->mvdev;
	s8 db = mvdev->dc->h2c_vdev_db;

	if (db != -1)
		mvdev->mdev->ops->send_intr(mvdev->mdev, db);
}

/* Determine the total number of bytes consumed in a VRINGH KIOV */
static inline u32 mic_vringh_iov_consumed(struct vringh_kiov *iov)
{
	int i;
	u32 total = iov->consumed;

	for (i = 0; i < iov->i; i++)
		total += iov->iov[i].iov_len;
	return total;
}

/*
 * Traverse the VRINGH KIOV and issue the APIs to trigger the copies.
 * This API is heavily based on the vringh_iov_xfer(..) implementation
 * in vringh.c. The reason we cannot reuse vringh_iov_pull_kern(..)
 * and vringh_iov_push_kern(..) directly is because there is no
 * way to override the VRINGH xfer(..) routines as of v3.10.
 */
static int mic_vringh_copy(struct mic_vdev *mvdev, struct vringh_kiov *iov,
	void __user *ubuf, size_t len, bool read, size_t *out_len)
{
	int ret = 0;
	size_t partlen, tot_len = 0;

	while (len && iov->i < iov->used) {
		partlen = min(iov->iov[iov->i].iov_len, len);
		if (read)
			ret = mic_virtio_copy_to_user(mvdev,
				ubuf, partlen,
				(u64)iov->iov[iov->i].iov_base);
		else
			ret = mic_virtio_copy_from_user(mvdev,
				ubuf, partlen,
				(u64)iov->iov[iov->i].iov_base);
		if (ret) {
			dev_err(mic_dev(mvdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			break;
		}
		len -= partlen;
		ubuf += partlen;
		tot_len += partlen;
		iov->consumed += partlen;
		iov->iov[iov->i].iov_len -= partlen;
		iov->iov[iov->i].iov_base += partlen;
		if (!iov->iov[iov->i].iov_len) {
			/* Fix up old iov element then increment. */
			iov->iov[iov->i].iov_len = iov->consumed;
			iov->iov[iov->i].iov_base -= iov->consumed;

			iov->consumed = 0;
			iov->i++;
		}
	}
	*out_len = tot_len;
	return ret;
}

/*
 * Use the standard VRINGH infrastructure in the kernel to fetch new
 * descriptors, initiate the copies and update the used ring.
 */
static int _mic_virtio_copy(struct mic_vdev *mvdev,
	struct mic_copy_desc *copy)
{
	int ret = 0, iovcnt = copy->iovcnt;
	struct iovec iov;
	struct iovec __user *u_iov = copy->iov;
	void __user *ubuf = NULL;
	struct mic_vringh *mvr = &mvdev->mvr[copy->vr_idx];
	struct vringh_kiov *riov = &mvr->riov;
	struct vringh_kiov *wiov = &mvr->wiov;
	struct vringh *vrh = &mvr->vrh;
	u16 *head = &mvr->head;
	struct mic_vring *vr = &mvr->vring;
	size_t len = 0, out_len;

	copy->out_len = 0;
	/* Fetch a new IOVEC if all previous elements have been processed */
	if (riov->i == riov->used && wiov->i == wiov->used) {
		ret = vringh_getdesc_kern(vrh, riov, wiov,
				head, GFP_KERNEL);
		/* Check if there are available descriptors */
		if (ret <= 0)
			return ret;
	}
	while (iovcnt) {
		if (!len) {
			/* Copy over a new iovec from user space. */
			ret = copy_from_user(&iov, u_iov, sizeof(*u_iov));
			if (ret) {
				ret = -EINVAL;
				dev_err(mic_dev(mvdev), "%s %d err %d\n",
					__func__, __LINE__, ret);
				break;
			}
			len = iov.iov_len;
			ubuf = iov.iov_base;
		}
		/* Issue all the read descriptors first */
		ret = mic_vringh_copy(mvdev, riov, ubuf, len,
			MIC_VRINGH_READ, &out_len);
		if (ret) {
			dev_err(mic_dev(mvdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			break;
		}
		len -= out_len;
		ubuf += out_len;
		copy->out_len += out_len;
		/* Issue the write descriptors next */
		ret = mic_vringh_copy(mvdev, wiov, ubuf, len,
			!MIC_VRINGH_READ, &out_len);
		if (ret) {
			dev_err(mic_dev(mvdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			break;
		}
		len -= out_len;
		ubuf += out_len;
		copy->out_len += out_len;
		if (!len) {
			/* One user space iovec is now completed */
			iovcnt--;
			u_iov++;
		}
		/* Exit loop if all elements in KIOVs have been processed. */
		if (riov->i == riov->used && wiov->i == wiov->used)
			break;
	}
	/*
	 * Update the used ring if a descriptor was available and some data was
	 * copied in/out and the user asked for a used ring update.
	 */
	if (*head != USHRT_MAX && copy->out_len && copy->update_used) {
		u32 total = 0;

		/* Determine the total data consumed */
		total += mic_vringh_iov_consumed(riov);
		total += mic_vringh_iov_consumed(wiov);
		vringh_complete_kern(vrh, *head, total);
		*head = USHRT_MAX;
		if (vringh_need_notify_kern(vrh) > 0)
			vringh_notify(vrh);
		vringh_kiov_cleanup(riov);
		vringh_kiov_cleanup(wiov);
		/* Update avail idx for user space */
		vr->info->avail_idx = vrh->last_avail_idx;
	}
	return ret;
}

static inline int mic_verify_copy_args(struct mic_vdev *mvdev,
		struct mic_copy_desc *copy)
{
	if (copy->vr_idx >= mvdev->dd->num_vq) {
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, -EINVAL);
		return -EINVAL;
	}
	return 0;
}

/* Copy a specified number of virtio descriptors in a chain */
int mic_virtio_copy_desc(struct mic_vdev *mvdev,
		struct mic_copy_desc *copy)
{
	int err;
	struct mic_vringh *mvr = &mvdev->mvr[copy->vr_idx];

	err = mic_verify_copy_args(mvdev, copy);
	if (err)
		return err;

	mutex_lock(&mvr->vr_mutex);
	if (!mic_vdevup(mvdev)) {
		err = -ENODEV;
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, err);
		goto err;
	}
	err = _mic_virtio_copy(mvdev, copy);
	if (err) {
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, err);
	}
err:
	mutex_unlock(&mvr->vr_mutex);
	return err;
}

static void mic_virtio_init_post(struct mic_vdev *mvdev)
{
	struct mic_vqconfig *vqconfig = mic_vq_config(mvdev->dd);
	int i;

	for (i = 0; i < mvdev->dd->num_vq; i++) {
		if (!le64_to_cpu(vqconfig[i].used_address)) {
			dev_warn(mic_dev(mvdev), "used_address zero??\n");
			continue;
		}
		mvdev->mvr[i].vrh.vring.used =
			mvdev->mdev->aper.va +
			le64_to_cpu(vqconfig[i].used_address);
	}

	mvdev->dc->used_address_updated = 0;

	dev_dbg(mic_dev(mvdev), "%s: device type %d LINKUP\n",
		__func__, mvdev->virtio_id);
}

static inline void mic_virtio_device_reset(struct mic_vdev *mvdev)
{
	int i;

	dev_dbg(mic_dev(mvdev), "%s: status %d device type %d RESET\n",
		__func__, mvdev->dd->status, mvdev->virtio_id);

	for (i = 0; i < mvdev->dd->num_vq; i++)
		/*
		 * Avoid lockdep false positive. The + 1 is for the mic
		 * mutex which is held in the reset devices code path.
		 */
		mutex_lock_nested(&mvdev->mvr[i].vr_mutex, i + 1);

	/* 0 status means "reset" */
	mvdev->dd->status = 0;
	mvdev->dc->vdev_reset = 0;
	mvdev->dc->host_ack = 1;

	for (i = 0; i < mvdev->dd->num_vq; i++) {
		struct vringh *vrh = &mvdev->mvr[i].vrh;
		mvdev->mvr[i].vring.info->avail_idx = 0;
		vrh->completed = 0;
		vrh->last_avail_idx = 0;
		vrh->last_used_idx = 0;
	}

	for (i = 0; i < mvdev->dd->num_vq; i++)
		mutex_unlock(&mvdev->mvr[i].vr_mutex);
}

void mic_virtio_reset_devices(struct mic_device *mdev)
{
	struct list_head *pos, *tmp;
	struct mic_vdev *mvdev;

	dev_dbg(mdev->sdev->parent, "%s\n",  __func__);

	list_for_each_safe(pos, tmp, &mdev->vdev_list) {
		mvdev = list_entry(pos, struct mic_vdev, list);
		mic_virtio_device_reset(mvdev);
		mvdev->poll_wake = 1;
		wake_up(&mvdev->waitq);
	}
}

void mic_bh_handler(struct work_struct *work)
{
	struct mic_vdev *mvdev = container_of(work, struct mic_vdev,
			virtio_bh_work);

	if (mvdev->dc->used_address_updated)
		mic_virtio_init_post(mvdev);

	if (mvdev->dc->vdev_reset)
		mic_virtio_device_reset(mvdev);

	mvdev->poll_wake = 1;
	wake_up(&mvdev->waitq);
}

static irqreturn_t mic_virtio_intr_handler(int irq, void *data)
{
	struct mic_vdev *mvdev = data;
	struct mic_device *mdev = mvdev->mdev;

	mdev->ops->ack_interrupt(mdev);
	schedule_work(&mvdev->virtio_bh_work);
	return IRQ_HANDLED;
}

int mic_virtio_config_change(struct mic_vdev *mvdev,
			void __user *argp)
{
	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
	int ret = 0, retry = 100, i;
	struct mic_bootparam *bootparam = mvdev->mdev->dp;
	s8 db = bootparam->h2c_config_db;

	mutex_lock(&mvdev->mdev->mic_mutex);
	for (i = 0; i < mvdev->dd->num_vq; i++)
		mutex_lock_nested(&mvdev->mvr[i].vr_mutex, i + 1);

	if (db == -1 || mvdev->dd->type == -1) {
		ret = -EIO;
		goto exit;
	}

	if (copy_from_user(mic_vq_configspace(mvdev->dd),
			   argp, mvdev->dd->config_len)) {
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, -EFAULT);
		ret = -EFAULT;
		goto exit;
	}
	mvdev->dc->config_change = MIC_VIRTIO_PARAM_CONFIG_CHANGED;
	mvdev->mdev->ops->send_intr(mvdev->mdev, db);

	for (i = retry; i--;) {
		ret = wait_event_timeout(wake,
			mvdev->dc->guest_ack, msecs_to_jiffies(100));
		if (ret)
			break;
	}

	dev_dbg(mic_dev(mvdev),
		"%s %d retry: %d\n", __func__, __LINE__, retry);
	mvdev->dc->config_change = 0;
	mvdev->dc->guest_ack = 0;
exit:
	for (i = 0; i < mvdev->dd->num_vq; i++)
		mutex_unlock(&mvdev->mvr[i].vr_mutex);
	mutex_unlock(&mvdev->mdev->mic_mutex);
	return ret;
}

static int mic_copy_dp_entry(struct mic_vdev *mvdev,
					void __user *argp,
					__u8 *type,
					struct mic_device_desc **devpage)
{
	struct mic_device *mdev = mvdev->mdev;
	struct mic_device_desc dd, *dd_config, *devp;
	struct mic_vqconfig *vqconfig;
	int ret = 0, i;
	bool slot_found = false;

	if (copy_from_user(&dd, argp, sizeof(dd))) {
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, -EFAULT);
		return -EFAULT;
	}

	if (mic_aligned_desc_size(&dd) > MIC_MAX_DESC_BLK_SIZE ||
	    dd.num_vq > MIC_MAX_VRINGS) {
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, -EINVAL);
		return -EINVAL;
	}

	dd_config = kmalloc(mic_desc_size(&dd), GFP_KERNEL);
	if (dd_config == NULL) {
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, -ENOMEM);
		return -ENOMEM;
	}
	if (copy_from_user(dd_config, argp, mic_desc_size(&dd))) {
		ret = -EFAULT;
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, ret);
		goto exit;
	}

	vqconfig = mic_vq_config(dd_config);
	for (i = 0; i < dd.num_vq; i++) {
		if (le16_to_cpu(vqconfig[i].num) > MIC_MAX_VRING_ENTRIES) {
			ret =  -EINVAL;
			dev_err(mic_dev(mvdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			goto exit;
		}
	}

	/* Find the first free device page entry */
	for (i = mic_aligned_size(struct mic_bootparam);
		i < MIC_DP_SIZE - mic_total_desc_size(dd_config);
		i += mic_total_desc_size(devp)) {
		devp = mdev->dp + i;
		if (devp->type == 0 || devp->type == -1) {
			slot_found = true;
			break;
		}
	}
	if (!slot_found) {
		ret =  -EINVAL;
		dev_err(mic_dev(mvdev), "%s %d err %d\n",
			__func__, __LINE__, ret);
		goto exit;
	}
	/*
	 * Save off the type before doing the memcpy. Type will be set in the
	 * end after completing all initialization for the new device.
	 */
	*type = dd_config->type;
	dd_config->type = 0;
	memcpy(devp, dd_config, mic_desc_size(dd_config));

	*devpage = devp;
exit:
	kfree(dd_config);
	return ret;
}

static void mic_init_device_ctrl(struct mic_vdev *mvdev,
				struct mic_device_desc *devpage)
{
	struct mic_device_ctrl *dc;

	dc = (void *)devpage + mic_aligned_desc_size(devpage);

	dc->config_change = 0;
	dc->guest_ack = 0;
	dc->vdev_reset = 0;
	dc->host_ack = 0;
	dc->used_address_updated = 0;
	dc->c2h_vdev_db = -1;
	dc->h2c_vdev_db = -1;
	mvdev->dc = dc;
}

int mic_virtio_add_device(struct mic_vdev *mvdev,
			void __user *argp)
{
	struct mic_device *mdev = mvdev->mdev;
	struct mic_device_desc *dd = NULL;
	struct mic_vqconfig *vqconfig;
	int vr_size, i, j, ret;
	u8 type = 0;
	s8 db;
	char irqname[10];
	struct mic_bootparam *bootparam = mdev->dp;
	u16 num;

	mutex_lock(&mdev->mic_mutex);

	ret = mic_copy_dp_entry(mvdev, argp, &type, &dd);
	if (ret) {
		mutex_unlock(&mdev->mic_mutex);
		return ret;
	}

	mic_init_device_ctrl(mvdev, dd);

	mvdev->dd = dd;
	mvdev->virtio_id = type;
	vqconfig = mic_vq_config(dd);
	INIT_WORK(&mvdev->virtio_bh_work, mic_bh_handler);

	for (i = 0; i < dd->num_vq; i++) {
		struct mic_vringh *mvr = &mvdev->mvr[i];
		struct mic_vring *vr = &mvdev->mvr[i].vring;
		num = le16_to_cpu(vqconfig[i].num);
		mutex_init(&mvr->vr_mutex);
		vr_size = PAGE_ALIGN(vring_size(num, MIC_VIRTIO_RING_ALIGN) +
			sizeof(struct _mic_vring_info));
		vr->va = (void *)
			__get_free_pages(GFP_KERNEL | __GFP_ZERO,
					 get_order(vr_size));
		if (!vr->va) {
			ret = -ENOMEM;
			dev_err(mic_dev(mvdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			goto err;
		}
		vr->len = vr_size;
		vr->info = vr->va + vring_size(num, MIC_VIRTIO_RING_ALIGN);
		vr->info->magic = MIC_MAGIC + mvdev->virtio_id + i;
		vqconfig[i].address = mic_map_single(mdev,
			vr->va, vr_size);
		if (mic_map_error(vqconfig[i].address)) {
			free_pages((unsigned long)vr->va, get_order(vr_size));
			ret = -ENOMEM;
			dev_err(mic_dev(mvdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			goto err;
		}
		vqconfig[i].address = cpu_to_le64(vqconfig[i].address);

		vring_init(&vr->vr, num, vr->va, MIC_VIRTIO_RING_ALIGN);
		ret = vringh_init_kern(&mvr->vrh,
			*(u32 *)mic_vq_features(mvdev->dd), num, false,
			vr->vr.desc, vr->vr.avail, vr->vr.used);
		if (ret) {
			dev_err(mic_dev(mvdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			goto err;
		}
		vringh_kiov_init(&mvr->riov, NULL, 0);
		vringh_kiov_init(&mvr->wiov, NULL, 0);
		mvr->head = USHRT_MAX;
		mvr->mvdev = mvdev;
		mvr->vrh.notify = mic_notify;
		dev_dbg(mdev->sdev->parent,
			"%s %d index %d va %p info %p vr_size 0x%x\n",
			__func__, __LINE__, i, vr->va, vr->info, vr_size);
	}

	snprintf(irqname, sizeof(irqname), "mic%dvirtio%d", mdev->id,
		 mvdev->virtio_id);
	mvdev->virtio_db = mic_next_db(mdev);
	mvdev->virtio_cookie = mic_request_irq(mdev, mic_virtio_intr_handler,
			irqname, mvdev, mvdev->virtio_db, MIC_INTR_DB);
	if (IS_ERR(mvdev->virtio_cookie)) {
		ret = PTR_ERR(mvdev->virtio_cookie);
		dev_dbg(mdev->sdev->parent, "request irq failed\n");
		goto err;
	}

	mvdev->dc->c2h_vdev_db = mvdev->virtio_db;

	list_add_tail(&mvdev->list, &mdev->vdev_list);
	/*
	 * Order the type update with previous stores. This write barrier
	 * is paired with the corresponding read barrier before the uncached
	 * system memory read of the type, on the card while scanning the
	 * device page.
	 */
	smp_wmb();
	dd->type = type;

	dev_dbg(mdev->sdev->parent, "Added virtio device id %d\n", dd->type);

	db = bootparam->h2c_config_db;
	if (db != -1)
		mdev->ops->send_intr(mdev, db);
	mutex_unlock(&mdev->mic_mutex);
	return 0;
err:
	vqconfig = mic_vq_config(dd);
	for (j = 0; j < i; j++) {
		struct mic_vringh *mvr = &mvdev->mvr[j];
		mic_unmap_single(mdev, le64_to_cpu(vqconfig[j].address),
				 mvr->vring.len);
		free_pages((unsigned long)mvr->vring.va,
			   get_order(mvr->vring.len));
	}
	mutex_unlock(&mdev->mic_mutex);
	return ret;
}

void mic_virtio_del_device(struct mic_vdev *mvdev)
{
	struct list_head *pos, *tmp;
	struct mic_vdev *tmp_mvdev;
	struct mic_device *mdev = mvdev->mdev;
	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
	int i, ret, retry = 100;
	struct mic_vqconfig *vqconfig;
	struct mic_bootparam *bootparam = mdev->dp;
	s8 db;

	mutex_lock(&mdev->mic_mutex);
	db = bootparam->h2c_config_db;
	if (db == -1)
		goto skip_hot_remove;
	dev_dbg(mdev->sdev->parent,
		"Requesting hot remove id %d\n", mvdev->virtio_id);
	mvdev->dc->config_change = MIC_VIRTIO_PARAM_DEV_REMOVE;
	mdev->ops->send_intr(mdev, db);
	for (i = retry; i--;) {
		ret = wait_event_timeout(wake,
			mvdev->dc->guest_ack, msecs_to_jiffies(100));
		if (ret)
			break;
	}
	dev_dbg(mdev->sdev->parent,
		"Device id %d config_change %d guest_ack %d\n",
		mvdev->virtio_id, mvdev->dc->config_change,
		mvdev->dc->guest_ack);
	mvdev->dc->config_change = 0;
	mvdev->dc->guest_ack = 0;
skip_hot_remove:
	mic_free_irq(mdev, mvdev->virtio_cookie, mvdev);
	flush_work(&mvdev->virtio_bh_work);
	vqconfig = mic_vq_config(mvdev->dd);
	for (i = 0; i < mvdev->dd->num_vq; i++) {
		struct mic_vringh *mvr = &mvdev->mvr[i];
		vringh_kiov_cleanup(&mvr->riov);
		vringh_kiov_cleanup(&mvr->wiov);
		mic_unmap_single(mdev, le64_to_cpu(vqconfig[i].address),
				 mvr->vring.len);
		free_pages((unsigned long)mvr->vring.va,
			   get_order(mvr->vring.len));
	}

	list_for_each_safe(pos, tmp, &mdev->vdev_list) {
		tmp_mvdev = list_entry(pos, struct mic_vdev, list);
		if (tmp_mvdev == mvdev) {
			list_del(pos);
			dev_dbg(mdev->sdev->parent,
				"Removing virtio device id %d\n",
				mvdev->virtio_id);
			break;
		}
	}
	/*
	 * Order the type update with previous stores. This write barrier
	 * is paired with the corresponding read barrier before the uncached
	 * system memory read of the type, on the card while scanning the
	 * device page.
	 */
	smp_wmb();
	mvdev->dd->type = -1;
	mutex_unlock(&mdev->mic_mutex);
}