xref: /linux/Documentation/userspace-api/vduse.rst (revision bb29b957eaaa15be33243550d9ee576c2d601669)
1==================================
2VDUSE - "vDPA Device in Userspace"
3==================================
4
5vDPA (virtio data path acceleration) device is a device that uses a
6datapath which complies with the virtio specifications with vendor
7specific control path. vDPA devices can be both physically located on
8the hardware or emulated by software. VDUSE is a framework that makes it
9possible to implement software-emulated vDPA devices in userspace. And
10to make the device emulation more secure, the emulated vDPA device's
11control path is handled in the kernel and only the data path is
12implemented in the userspace.
13
14Note that virtio block, network, and filesystem device types are supported
15by the VDUSE framework. Other device types may be added after the security
16implications of the corresponding device driver are clarified or fixed in
17the future.
18
19Create/Destroy VDUSE devices
20----------------------------
21
22VDUSE devices are created as follows:
23
241. Create a new VDUSE instance with ioctl(VDUSE_CREATE_DEV) on
25   /dev/vduse/control.
26
272. Setup each virtqueue with ioctl(VDUSE_VQ_SETUP) on /dev/vduse/$NAME.
28
293. Begin processing VDUSE messages from /dev/vduse/$NAME. The first
30   messages will arrive while attaching the VDUSE instance to vDPA bus.
31
324. Send the VDPA_CMD_DEV_NEW netlink message to attach the VDUSE
33   instance to vDPA bus.
34
35VDUSE devices are destroyed as follows:
36
371. Send the VDPA_CMD_DEV_DEL netlink message to detach the VDUSE
38   instance from vDPA bus.
39
402. Close the file descriptor referring to /dev/vduse/$NAME.
41
423. Destroy the VDUSE instance with ioctl(VDUSE_DESTROY_DEV) on
43   /dev/vduse/control.
44
45The netlink messages can be sent via vdpa tool in iproute2 or use the
46below sample codes:
47
48.. code-block:: c
49
50	static int netlink_add_vduse(const char *name, enum vdpa_command cmd)
51	{
52		struct nl_sock *nlsock;
53		struct nl_msg *msg;
54		int famid;
55
56		nlsock = nl_socket_alloc();
57		if (!nlsock)
58			return -ENOMEM;
59
60		if (genl_connect(nlsock))
61			goto free_sock;
62
63		famid = genl_ctrl_resolve(nlsock, VDPA_GENL_NAME);
64		if (famid < 0)
65			goto close_sock;
66
67		msg = nlmsg_alloc();
68		if (!msg)
69			goto close_sock;
70
71		if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, famid, 0, 0, cmd, 0))
72			goto nla_put_failure;
73
74		NLA_PUT_STRING(msg, VDPA_ATTR_DEV_NAME, name);
75		if (cmd == VDPA_CMD_DEV_NEW)
76			NLA_PUT_STRING(msg, VDPA_ATTR_MGMTDEV_DEV_NAME, "vduse");
77
78		if (nl_send_sync(nlsock, msg))
79			goto close_sock;
80
81		nl_close(nlsock);
82		nl_socket_free(nlsock);
83
84		return 0;
85	nla_put_failure:
86		nlmsg_free(msg);
87	close_sock:
88		nl_close(nlsock);
89	free_sock:
90		nl_socket_free(nlsock);
91		return -1;
92	}
93
94How VDUSE works
95---------------
96
97As mentioned above, a VDUSE device is created by ioctl(VDUSE_CREATE_DEV) on
98/dev/vduse/control. With this ioctl, userspace can specify some basic configuration
99such as device name (uniquely identify a VDUSE device), virtio features, virtio
100configuration space, the number of virtqueues and so on for this emulated device.
101Then a char device interface (/dev/vduse/$NAME) is exported to userspace for device
102emulation. Userspace can use the VDUSE_VQ_SETUP ioctl on /dev/vduse/$NAME to
103add per-virtqueue configuration such as the max size of virtqueue to the device.
104
105After the initialization, the VDUSE device can be attached to vDPA bus via
106the VDPA_CMD_DEV_NEW netlink message. Userspace needs to read()/write() on
107/dev/vduse/$NAME to receive/reply some control messages from/to VDUSE kernel
108module as follows:
109
110.. code-block:: c
111
112	static int vduse_message_handler(int dev_fd)
113	{
114		int len;
115		struct vduse_dev_request req;
116		struct vduse_dev_response resp;
117
118		len = read(dev_fd, &req, sizeof(req));
119		if (len != sizeof(req))
120			return -1;
121
122		resp.request_id = req.request_id;
123
124		switch (req.type) {
125
126		/* handle different types of messages */
127
128		}
129
130		len = write(dev_fd, &resp, sizeof(resp));
131		if (len != sizeof(resp))
132			return -1;
133
134		return 0;
135	}
136
137The following control message types may be delivered via read(2) on
138/dev/vduse/$NAME:
139
140- VDUSE_GET_VQ_STATE: Get the state for virtqueue, userspace should return
141  avail index for split virtqueue or the device/driver ring wrap counters and
142  the avail and used index for packed virtqueue.
143
144- VDUSE_SET_STATUS: Set the device status, userspace should follow
145  the virtio spec: https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html
146  to process this message. For example, fail to set the FEATURES_OK device
147  status bit if the device can not accept the negotiated virtio features
148  get from the VDUSE_DEV_GET_FEATURES ioctl.
149
150- VDUSE_UPDATE_IOTLB: Notify userspace to update the memory mapping for specified
151  IOVA range, userspace should firstly remove the old mapping, then setup the new
152  mapping via the VDUSE_IOTLB_GET_FD ioctl.
153
154- VDUSE_SET_VQ_GROUP_ASID: Notify userspace to change the address space of a
155  virtqueue group (API version 1).
156
157- VDUSE_SET_VQ_READY: Notify userspace that a virtqueue should become ready or
158  not ready (when VDUSE_F_QUEUE_READY is negotiated).
159
160- VDUSE_SUSPEND: Notify userspace that the device is being suspended (when
161  VDUSE_F_SUSPEND is negotiated).
162
163After DRIVER_OK status bit is set via the VDUSE_SET_STATUS message, userspace is
164able to start the dataplane processing as follows:
165
1661. Get the specified virtqueue's information with the VDUSE_VQ_GET_INFO ioctl,
167   including the size, the IOVAs of descriptor table, available ring and used ring,
168   the state and the ready status.
169
1702. Pass the above IOVAs to the VDUSE_IOTLB_GET_FD ioctl so that those IOVA regions
171   can be mapped into userspace. Some sample codes is shown below:
172
173.. code-block:: c
174
175	static int perm_to_prot(uint8_t perm)
176	{
177		int prot = 0;
178
179		switch (perm) {
180		case VDUSE_ACCESS_WO:
181			prot |= PROT_WRITE;
182			break;
183		case VDUSE_ACCESS_RO:
184			prot |= PROT_READ;
185			break;
186		case VDUSE_ACCESS_RW:
187			prot |= PROT_READ | PROT_WRITE;
188			break;
189		}
190
191		return prot;
192	}
193
194	static void *iova_to_va(int dev_fd, uint64_t iova, uint64_t *len)
195	{
196		int fd;
197		void *addr;
198		size_t size;
199		struct vduse_iotlb_entry entry;
200
201		entry.start = iova;
202		entry.last = iova;
203
204		/*
205		 * Find the first IOVA region that overlaps with the specified
206		 * range [start, last] and return the corresponding file descriptor.
207		 */
208		fd = ioctl(dev_fd, VDUSE_IOTLB_GET_FD, &entry);
209		if (fd < 0)
210			return NULL;
211
212		size = entry.last - entry.start + 1;
213		*len = entry.last - iova + 1;
214		addr = mmap(0, size, perm_to_prot(entry.perm), MAP_SHARED,
215			    fd, entry.offset);
216		close(fd);
217		if (addr == MAP_FAILED)
218			return NULL;
219
220		/*
221		 * Using some data structures such as linked list to store
222		 * the iotlb mapping. The munmap(2) should be called for the
223		 * cached mapping when the corresponding VDUSE_UPDATE_IOTLB
224		 * message is received or the device is reset.
225		 */
226
227		return addr + iova - entry.start;
228	}
229
2303. Setup the kick eventfd for the specified virtqueues with the VDUSE_VQ_SETUP_KICKFD
231   ioctl. The kick eventfd is used by VDUSE kernel module to notify userspace to
232   consume the available ring. This is optional since userspace can choose to poll the
233   available ring instead.
234
2354. Listen to the kick eventfd (optional) and consume the available ring. The buffer
236   described by the descriptors in the descriptor table should be also mapped into
237   userspace via the VDUSE_IOTLB_GET_FD ioctl before accessing.
238
2395. Inject an interrupt for specific virtqueue with the VDUSE_VQ_INJECT_IRQ ioctl
240   after the used ring is filled.
241
242Enabling ASID (API version 1)
243------------------------------
244
245VDUSE supports per-address-space identifiers (ASIDs) starting with API
246version 1. Set it up with ioctl(VDUSE_SET_API_VERSION) on `/dev/vduse/control`
247and pass `VDUSE_API_VERSION_1` before creating a new VDUSE instance with
248ioctl(VDUSE_CREATE_DEV).
249
250Afterwards, ioctl(VDUSE_VQ_SETUP) takes a virtqueue group index in
251struct vduse_vq_config::group.  The driver can change the address space
252of any virtqueue group by using the VDUSE_SET_VQ_GROUP_ASID message type,
253and the VDUSE instance needs to reply with VDUSE_REQ_RESULT_OK if it was
254possible to change it.
255
256Similarly, you can use ioctl(VDUSE_IOTLB_GET_FD2) to obtain the file descriptor
257describing an IOVA region of a specific ASID. Example usage:
258
259.. code-block:: c
260
261	static void *iova_to_va(int dev_fd, uint32_t asid, uint64_t iova,
262	                        uint64_t *len)
263	{
264		int fd;
265		void *addr;
266		size_t size;
267		struct vduse_iotlb_entry_v2 entry = { 0 };
268
269		entry.v1.start = iova;
270		entry.v1.last = iova;
271		entry.asid = asid;
272
273		fd = ioctl(dev_fd, VDUSE_IOTLB_GET_FD2, &entry);
274		if (fd < 0)
275			return NULL;
276
277		size = entry.v1.last - entry.v1.start + 1;
278		*len = entry.v1.last - iova + 1;
279		addr = mmap(0, size, perm_to_prot(entry.v1.perm), MAP_SHARED,
280			    fd, entry.v1.offset);
281		close(fd);
282		if (addr == MAP_FAILED)
283			return NULL;
284
285		/*
286		 * Using some data structures such as linked list to store
287		 * the iotlb mapping. The munmap(2) should be called for the
288		 * cached mapping when the corresponding VDUSE_UPDATE_IOTLB
289		 * message is received or the device is reset.
290		 */
291
292		return addr + iova - entry.v1.start;
293	}
294
295For more details on the uAPI, please see include/uapi/linux/vduse.h.
296