diff options
Diffstat (limited to 'drivers/misc/mic/host/mic_virtio.c')
| -rw-r--r-- | drivers/misc/mic/host/mic_virtio.c | 700 | 
1 files changed, 700 insertions, 0 deletions
diff --git a/drivers/misc/mic/host/mic_virtio.c b/drivers/misc/mic/host/mic_virtio.c new file mode 100644 index 000000000000..5b8494bd1e00 --- /dev/null +++ b/drivers/misc/mic/host/mic_virtio.c @@ -0,0 +1,700 @@ +/* + * 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); +}  | 
