Skip to content
virtio_uml.c 32.4 KiB
Newer Older
		if (!names[i]) {
			vqs[i] = NULL;
			continue;
		}

		vqs[i] = vu_setup_vq(vdev, queue_idx++, callbacks[i], names[i],
				     ctx ? ctx[i] : false);
		if (IS_ERR(vqs[i])) {
			rc = PTR_ERR(vqs[i]);
			goto error_setup;
		}
	}

	list_for_each_entry(vq, &vdev->vqs, list) {
		struct virtio_uml_vq_info *info = vq->priv;

		if (info->kick_fd >= 0) {
			rc = vhost_user_set_vring_kick(vu_dev, vq->index,
						       info->kick_fd);
			if (rc)
				goto error_setup;
		}

		rc = vhost_user_set_vring_enable(vu_dev, vq->index, true);
		if (rc)
			goto error_setup;
	}

	return 0;

error_setup:
	vu_del_vqs(vdev);
	return rc;
}

static u64 vu_get_features(struct virtio_device *vdev)
{
	struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev);

	return vu_dev->features;
}

static int vu_finalize_features(struct virtio_device *vdev)
{
	struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev);
	u64 supported = vdev->features & VHOST_USER_SUPPORTED_F;

	vring_transport_features(vdev);
	vu_dev->features = vdev->features | supported;

	return vhost_user_set_features(vu_dev, vu_dev->features);
}

static const char *vu_bus_name(struct virtio_device *vdev)
{
	struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev);

	return vu_dev->pdev->name;
}

static const struct virtio_config_ops virtio_uml_config_ops = {
	.get = vu_get,
	.set = vu_set,
	.get_status = vu_get_status,
	.set_status = vu_set_status,
	.reset = vu_reset,
	.find_vqs = vu_find_vqs,
	.del_vqs = vu_del_vqs,
	.get_features = vu_get_features,
	.finalize_features = vu_finalize_features,
	.bus_name = vu_bus_name,
};

static void virtio_uml_release_dev(struct device *d)
{
	struct virtio_device *vdev =
			container_of(d, struct virtio_device, dev);
	struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev);

	/* might not have been opened due to not negotiating the feature */
	if (vu_dev->req_fd >= 0) {
		um_free_irq(vu_dev->irq, vu_dev);
	os_close_file(vu_dev->sock);
}

/* Platform device */

static int virtio_uml_probe(struct platform_device *pdev)
{
	struct virtio_uml_platform_data *pdata = pdev->dev.platform_data;
	struct virtio_uml_device *vu_dev;
	int rc;

	if (!pdata)
		return -EINVAL;

	vu_dev = kzalloc(sizeof(*vu_dev), GFP_KERNEL);
	if (!vu_dev)
		return -ENOMEM;

	vu_dev->vdev.dev.parent = &pdev->dev;
	vu_dev->vdev.dev.release = virtio_uml_release_dev;
	vu_dev->vdev.config = &virtio_uml_config_ops;
	vu_dev->vdev.id.device = pdata->virtio_device_id;
	vu_dev->vdev.id.vendor = VIRTIO_DEV_ANY_ID;
	vu_dev->pdev = pdev;

	do {
		rc = os_connect_socket(pdata->socket_path);
	} while (rc == -EINTR);
	if (rc < 0)
		return rc;
	vu_dev->sock = rc;

	spin_lock_init(&vu_dev->sock_lock);

	rc = vhost_user_init(vu_dev);
	if (rc)
		goto error_init;

	platform_set_drvdata(pdev, vu_dev);

	rc = register_virtio_device(&vu_dev->vdev);
	if (rc)
		put_device(&vu_dev->vdev.dev);
	vu_dev->registered = 1;
	return rc;

error_init:
	os_close_file(vu_dev->sock);
	return rc;
}

static int virtio_uml_remove(struct platform_device *pdev)
{
	struct virtio_uml_device *vu_dev = platform_get_drvdata(pdev);

	unregister_virtio_device(&vu_dev->vdev);
	return 0;
}

/* Command line device list */

static void vu_cmdline_release_dev(struct device *d)
{
}

static struct device vu_cmdline_parent = {
	.init_name = "virtio-uml-cmdline",
	.release = vu_cmdline_release_dev,
};

static bool vu_cmdline_parent_registered;
static int vu_cmdline_id;

static int vu_unregister_cmdline_device(struct device *dev, void *data)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct virtio_uml_platform_data *pdata = pdev->dev.platform_data;

	kfree(pdata->socket_path);
	platform_device_unregister(pdev);
	return 0;
}

static void vu_conn_broken(struct work_struct *wk)
{
	struct virtio_uml_platform_data *pdata;

	pdata = container_of(wk, struct virtio_uml_platform_data, conn_broken_wk);
	vu_unregister_cmdline_device(&pdata->pdev->dev, NULL);
}

static int vu_cmdline_set(const char *device, const struct kernel_param *kp)
{
	const char *ids = strchr(device, ':');
	unsigned int virtio_device_id;
	int processed, consumed, err;
	char *socket_path;
	struct virtio_uml_platform_data pdata, *ppdata;
	struct platform_device *pdev;

	if (!ids || ids == device)
		return -EINVAL;

	processed = sscanf(ids, ":%u%n:%d%n",
			   &virtio_device_id, &consumed,
			   &vu_cmdline_id, &consumed);

	if (processed < 1 || ids[consumed])
		return -EINVAL;

	if (!vu_cmdline_parent_registered) {
		err = device_register(&vu_cmdline_parent);
		if (err) {
			pr_err("Failed to register parent device!\n");
			put_device(&vu_cmdline_parent);
			return err;
		}
		vu_cmdline_parent_registered = true;
	}

	socket_path = kmemdup_nul(device, ids - device, GFP_KERNEL);
	if (!socket_path)
		return -ENOMEM;

	pdata.virtio_device_id = (u32) virtio_device_id;
	pdata.socket_path = socket_path;

	pr_info("Registering device virtio-uml.%d id=%d at %s\n",
		vu_cmdline_id, virtio_device_id, socket_path);

	pdev = platform_device_register_data(&vu_cmdline_parent, "virtio-uml",
					     vu_cmdline_id++, &pdata,
					     sizeof(pdata));
	err = PTR_ERR_OR_ZERO(pdev);
	if (err)
		goto free;

	ppdata = pdev->dev.platform_data;
	ppdata->pdev = pdev;
	INIT_WORK(&ppdata->conn_broken_wk, vu_conn_broken);

	return 0;

free:
	kfree(socket_path);
	return err;
}

static int vu_cmdline_get_device(struct device *dev, void *data)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct virtio_uml_platform_data *pdata = pdev->dev.platform_data;
	char *buffer = data;
	unsigned int len = strlen(buffer);

	snprintf(buffer + len, PAGE_SIZE - len, "%s:%d:%d\n",
		 pdata->socket_path, pdata->virtio_device_id, pdev->id);
	return 0;
}

static int vu_cmdline_get(char *buffer, const struct kernel_param *kp)
{
	buffer[0] = '\0';
	if (vu_cmdline_parent_registered)
		device_for_each_child(&vu_cmdline_parent, buffer,
				      vu_cmdline_get_device);
	return strlen(buffer) + 1;
}

static const struct kernel_param_ops vu_cmdline_param_ops = {
	.set = vu_cmdline_set,
	.get = vu_cmdline_get,
};

device_param_cb(device, &vu_cmdline_param_ops, NULL, S_IRUSR);
__uml_help(vu_cmdline_param_ops,
"virtio_uml.device=<socket>:<virtio_id>[:<platform_id>]\n"
"    Configure a virtio device over a vhost-user socket.\n"
"    See virtio_ids.h for a list of possible virtio device id values.\n"
"    Optionally use a specific platform_device id.\n\n"
);


static void vu_unregister_cmdline_devices(void)
{
	if (vu_cmdline_parent_registered) {
		device_for_each_child(&vu_cmdline_parent, NULL,
				      vu_unregister_cmdline_device);
		device_unregister(&vu_cmdline_parent);
		vu_cmdline_parent_registered = false;
	}
}

/* Platform driver */

static const struct of_device_id virtio_uml_match[] = {
	{ .compatible = "virtio,uml", },
	{ }
};
MODULE_DEVICE_TABLE(of, virtio_uml_match);

static struct platform_driver virtio_uml_driver = {
	.probe = virtio_uml_probe,
	.remove = virtio_uml_remove,
	.driver = {
		.name = "virtio-uml",
		.of_match_table = virtio_uml_match,
	},
};

static int __init virtio_uml_init(void)
{
	return platform_driver_register(&virtio_uml_driver);
}

static void __exit virtio_uml_exit(void)
{
	platform_driver_unregister(&virtio_uml_driver);
	vu_unregister_cmdline_devices();
}

module_init(virtio_uml_init);
module_exit(virtio_uml_exit);
__uml_exitcall(virtio_uml_exit);

MODULE_DESCRIPTION("UML driver for vhost-user virtio devices");
MODULE_LICENSE("GPL");