요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
.. _writing_virtio_drivers:
======================
Writing Virtio Drivers
======================
Introduction
============
This document serves as a basic guideline for driver programmers that
need to hack a new virtio driver or understand the essentials of the
existing ones. See :ref:`Virtio on Linux <virtio>` for a general
overview of virtio.
Driver boilerplate
==================
As a bare minimum, a virtio driver needs to register in the virtio bus
and configure the virtqueues for the device according to its spec, the
configuration of the virtqueues in the driver side must match the
virtqueue definitions in the device. A basic driver skeleton could look
like this::
#include <linux/virtio.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_config.h>
#include <linux/module.h>
/* device private data (one per device) */
struct virtio_dummy_dev {
struct virtqueue *vq;
};
static void virtio_dummy_recv_cb(struct virtqueue *vq)
{
struct virtio_dummy_dev *dev = vq->vdev->priv;
char *buf;
unsigned int len;
while ((buf = virtqueue_get_buf(dev->vq, &len)) != NULL) {
/* process the received data */
}
}
static int virtio_dummy_probe(struct virtio_device *vdev)
{
struct virtio_dummy_dev *dev = NULL;
/* initialize device data */
dev = kzalloc(sizeof(struct virtio_dummy_dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
/* the device has a single virtqueue */
dev->vq = virtio_find_single_vq(vdev, virtio_dummy_recv_cb, "input");
if (IS_ERR(dev->vq)) {
kfree(dev);
return PTR_ERR(dev->vq);
}
vdev->priv = dev;
/* from this point on, the device can notify and get callbacks */
virtio_device_ready(vdev);
return 0;
}
static void virtio_dummy_remove(struct virtio_device *vdev)
{
struct virtio_dummy_dev *dev = vdev->priv;
/*
* disable vq interrupts: equivalent to
* vdev->config->reset(vdev)
*/
virtio_reset_device(vdev);
/* detach unused buffers */
while ((buf = virtqueue_detach_unused_buf(dev->vq)) != NULL) {
kfree(buf);
}
/* remove virtqueues */
vdev->config->del_vqs(vdev);
kfree(dev);
}
static const struct virtio_device_id id_table[] = {
{ VIRTIO_ID_DUMMY, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static struct virtio_driver virtio_dummy_driver = {
.driver.name = KBUILD_MODNAME,
.id_table = id_table,
.probe = virtio_dummy_probe,
.remove = virtio_dummy_remove,
};
module_virtio_driver(virtio_dummy_driver);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Dummy virtio driver");
MODULE_LICENSE("GPL");
The device id ``VIRTIO_ID_DUMMY`` here is a placeholder, virtio drivers
should be added only for devices that are defined in the spec, see
include/uapi/linux/virtio_ids.h. Device ids need to be at least reserved
in the virtio spec before being added to that file.
If your driver doesn't have to do anything special in its ``init`` and
``exit`` methods, you can use the module_virtio_driver() helper to
reduce the amount of boilerplate code.
The ``probe`` method does the minimum driver setup in this case
(memory allocation for the device data) and initializes the
virtqueue. virtio_device_ready() is used to enable the virtqueue and to
notify the device that the driver is ready to manage the device
("DRIVER_OK"). The virtqueues are anyway enabled automatically by the
core after ``probe`` returns.
.. kernel-doc:: include/linux/virtio_config.h
:identifiers: virtio_device_ready
In any case, the virtqueues need to be enabled before adding buffers to
them.
Sending and receiving data
==========================
The virtio_dummy_recv_cb() callback in the code above will be triggered
when the device notifies the driver after it finishes processing a
descriptor or descriptor chain, either for reading or writing. However,
that's only the second half of the virtio device-driver communication
process, as the communication is always started by the driver regardless
of the direction of the data transfer.
To configure a buffer transfer from the driver to the device, first you
have to add the buffers -- packed as `scatterlists` -- to the
appropriate virtqueue using any of the virtqueue_add_inbuf(),
virtqueue_add_outbuf() or virtqueue_add_sgs(), depending on whether you
need to add one input `scatterlist` (for the device to fill in), one
output `scatterlist` (for the device to consume) or multiple
`scatterlists`, respectively. Then, once the virtqueue is set up, a call
to virtqueue_kick() sends a notification that will be serviced by the
hypervisor that implements the device::
struct scatterlist sg[1];
sg_init_one(sg, buffer, BUFLEN);
virtqueue_add_inbuf(dev->vq, sg, 1, buffer, GFP_ATOMIC);
virtqueue_kick(dev->vq);
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_add_inbuf
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_add_outbuf
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_add_sgs
Then, after the device has read or written the buffers prepared by the
driver and notifies it back, the driver can call virtqueue_get_buf() to
read the data produced by the device (if the virtqueue was set up with
input buffers) or simply to reclaim the buffers if they were already
consumed by the device:
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_get_buf_ctx
The virtqueue callbacks can be disabled and re-enabled using the
virtqueue_disable_cb() and the family of virtqueue_enable_cb() functions
respectively. See drivers/virtio/virtio_ring.c for more details:
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_disable_cb
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_enable_cb
But note that some spurious callbacks can still be triggered under
certain scenarios. The way to disable callbacks reliably is to reset the
device or the virtqueue (virtio_reset_device()).
References
==========
_`[1]` Virtio Spec v1.2:
https://docs.oasis-open.org/virtio/virtio/v1.2/virtio-v1.2.html
Check for later versions of the spec as well.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Virtio driver 작성 지침의 범위
1-17이 문서는 새 virtio driver를 구현하거나 기존 driver의 핵심을 이해해야 하는 programmer를 위한 기본 지침입니다. Virtio 전체 구조는 `Virtio on Linux` 문서를 먼저 참조합니다.
.. SPDX-License-Identifier: GPL-2.0
.. _writing_virtio_drivers:
======================
Writing Virtio Drivers
======================
Introduction
============
This document serves as a basic guideline for driver programmers that
need to hack a new virtio driver or understand the essentials of the
existing ones. See :ref:`Virtio on Linux <virtio>` for a general
overview of virtio.
최소 driver skeleton과 probe
18-70최소한의 virtio driver는 virtio bus에 등록하고 device specification에 맞춰 virtqueue를 구성해야 합니다. Driver 쪽 queue 구성은 device가 정의한 virtqueue와 정확히 일치해야 합니다.
예제의 `struct virtio_dummy_dev`는 device마다 하나씩 존재하는 private data이며 `struct virtqueue *vq`를 보관합니다. Completion callback `virtio_dummy_recv_cb()`는 `vq->vdev->priv`에서 private data를 얻고 `virtqueue_get_buf()`가 반환하는 처리 완료 buffer를 반복해서 회수합니다.
`virtio_dummy_probe()`는 private data를 `kzalloc()`으로 할당하고 `virtio_find_single_vq()`로 이름이 `input`인 단일 queue를 찾습니다. 실패하면 private data를 해제하고 error pointer의 errno를 반환합니다.
성공하면 `vdev->priv`에 private data를 저장하고 `virtio_device_ready()`를 호출합니다. 이 지점부터 device가 notify할 수 있고 driver callback도 실행될 수 있습니다.
Private state를 만든 뒤 specification과 일치하는 queue를 찾고 마지막에 device를 ready 상태로 전환합니다.
Driver boilerplate
==================
As a bare minimum, a virtio driver needs to register in the virtio bus
and configure the virtqueues for the device according to its spec, the
configuration of the virtqueues in the driver side must match the
virtqueue definitions in the device. A basic driver skeleton could look
like this::
#include <linux/virtio.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_config.h>
#include <linux/module.h>
/* device private data (one per device) */
struct virtio_dummy_dev {
struct virtqueue *vq;
};
static void virtio_dummy_recv_cb(struct virtqueue *vq)
{
struct virtio_dummy_dev *dev = vq->vdev->priv;
char *buf;
unsigned int len;
while ((buf = virtqueue_get_buf(dev->vq, &len)) != NULL) {
/* process the received data */
}
}
static int virtio_dummy_probe(struct virtio_device *vdev)
{
struct virtio_dummy_dev *dev = NULL;
/* initialize device data */
dev = kzalloc(sizeof(struct virtio_dummy_dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
/* the device has a single virtqueue */
dev->vq = virtio_find_single_vq(vdev, virtio_dummy_recv_cb, "input");
if (IS_ERR(dev->vq)) {
kfree(dev);
return PTR_ERR(dev->vq);
}
vdev->priv = dev;
/* from this point on, the device can notify and get callbacks */
virtio_device_ready(vdev);
return 0;
}
Remove 경로와 driver 등록
71-109`virtio_dummy_remove()`는 먼저 `virtio_reset_device()`로 virtqueue interrupt를 비활성화합니다. 이는 `vdev->config->reset(vdev)`와 동등한 효과를 냅니다.
그 다음 `virtqueue_detach_unused_buf()`로 아직 사용되지 않은 buffer를 모두 분리해 해제하고, `vdev->config->del_vqs(vdev)`로 virtqueue를 제거한 뒤 private data를 해제합니다.
`id_table`은 placeholder `VIRTIO_ID_DUMMY`와 `VIRTIO_DEV_ANY_ID`를 사용합니다. `struct virtio_driver`에는 module name, ID table, `probe`, `remove` callback을 지정합니다.
`module_virtio_driver()`가 module init/exit와 virtio bus 등록 boilerplate를 만들고, `MODULE_DEVICE_TABLE`, description, GPL license metadata를 함께 선언합니다.
Remove는 notification을 먼저 멈춘 뒤 buffer, queue, private state 순서로 정리합니다.
static void virtio_dummy_remove(struct virtio_device *vdev)
{
struct virtio_dummy_dev *dev = vdev->priv;
/*
* disable vq interrupts: equivalent to
* vdev->config->reset(vdev)
*/
virtio_reset_device(vdev);
/* detach unused buffers */
while ((buf = virtqueue_detach_unused_buf(dev->vq)) != NULL) {
kfree(buf);
}
/* remove virtqueues */
vdev->config->del_vqs(vdev);
kfree(dev);
}
static const struct virtio_device_id id_table[] = {
{ VIRTIO_ID_DUMMY, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static struct virtio_driver virtio_dummy_driver = {
.driver.name = KBUILD_MODNAME,
.id_table = id_table,
.probe = virtio_dummy_probe,
.remove = virtio_dummy_remove,
};
module_virtio_driver(virtio_dummy_driver);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Dummy virtio driver");
MODULE_LICENSE("GPL");
Device ID 예약과 DRIVER_OK 전환
110-131예제의 `VIRTIO_ID_DUMMY`는 placeholder입니다. Virtio driver는 specification에 정의된 device에만 추가해야 하며 ID 목록은 `include/uapi/linux/virtio_ids.h`에 있습니다. 이 file에 ID를 넣기 전에 virtio specification에서 최소한 해당 ID를 reserve해야 합니다.
Driver의 `init`과 `exit`에서 특별한 처리가 필요하지 않으면 `module_virtio_driver()` helper를 사용해 boilerplate code를 줄일 수 있습니다.
예제 `probe`는 device private memory를 할당하고 virtqueue를 초기화하는 최소 setup을 수행합니다. `virtio_device_ready()`는 queue를 enable하고 driver가 device를 관리할 준비가 되었다는 `DRIVER_OK` 상태를 device에 알립니다.
Core는 `probe`가 반환된 뒤에도 virtqueue를 자동 enable하지만, 어떠한 경우에도 queue에 buffer를 추가하기 전에 virtqueue가 enable되어 있어야 합니다.
The device id ``VIRTIO_ID_DUMMY`` here is a placeholder, virtio drivers
should be added only for devices that are defined in the spec, see
include/uapi/linux/virtio_ids.h. Device ids need to be at least reserved
in the virtio spec before being added to that file.
If your driver doesn't have to do anything special in its ``init`` and
``exit`` methods, you can use the module_virtio_driver() helper to
reduce the amount of boilerplate code.
The ``probe`` method does the minimum driver setup in this case
(memory allocation for the device data) and initializes the
virtqueue. virtio_device_ready() is used to enable the virtqueue and to
notify the device that the driver is ready to manage the device
("DRIVER_OK"). The virtqueues are anyway enabled automatically by the
core after ``probe`` returns.
.. kernel-doc:: include/linux/virtio_config.h
:identifiers: virtio_device_ready
In any case, the virtqueues need to be enabled before adding buffers to
them.
Scatterlist buffer 제출과 kick
132-170앞선 `virtio_dummy_recv_cb()`는 device가 descriptor 또는 descriptor chain의 read/write 처리를 끝내고 driver에 notify할 때 실행됩니다. 하지만 이것은 통신의 후반부이며 data direction과 관계없이 virtio device-driver communication은 항상 driver가 시작합니다.
Driver는 먼저 buffer를 scatterlist로 묶어 queue에 넣습니다. Device가 채울 input scatterlist 하나에는 `virtqueue_add_inbuf()`, device가 소비할 output scatterlist 하나에는 `virtqueue_add_outbuf()`, 여러 scatterlist에는 `virtqueue_add_sgs()`를 사용합니다.
Queue entry를 준비한 뒤 `virtqueue_kick()`을 호출하면 device를 구현한 hypervisor에 notification이 전달됩니다. 예제는 `sg_init_one()`으로 buffer 하나를 만들고 `virtqueue_add_inbuf()`로 input queue에 넣은 뒤 kick합니다.
Device가 buffer를 읽거나 쓴 뒤 다시 notify하면, input buffer에서는 `virtqueue_get_buf()`로 device가 만든 data를 읽고 output buffer에서는 이미 소비된 buffer를 회수합니다.
Driver가 descriptor를 제출하고 kick한 뒤 device completion notification에서 buffer를 회수합니다.
Sending and receiving data
==========================
The virtio_dummy_recv_cb() callback in the code above will be triggered
when the device notifies the driver after it finishes processing a
descriptor or descriptor chain, either for reading or writing. However,
that's only the second half of the virtio device-driver communication
process, as the communication is always started by the driver regardless
of the direction of the data transfer.
To configure a buffer transfer from the driver to the device, first you
have to add the buffers -- packed as `scatterlists` -- to the
appropriate virtqueue using any of the virtqueue_add_inbuf(),
virtqueue_add_outbuf() or virtqueue_add_sgs(), depending on whether you
need to add one input `scatterlist` (for the device to fill in), one
output `scatterlist` (for the device to consume) or multiple
`scatterlists`, respectively. Then, once the virtqueue is set up, a call
to virtqueue_kick() sends a notification that will be serviced by the
hypervisor that implements the device::
struct scatterlist sg[1];
sg_init_one(sg, buffer, BUFLEN);
virtqueue_add_inbuf(dev->vq, sg, 1, buffer, GFP_ATOMIC);
virtqueue_kick(dev->vq);
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_add_inbuf
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_add_outbuf
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_add_sgs
Then, after the device has read or written the buffers prepared by the
driver and notifies it back, the driver can call virtqueue_get_buf() to
read the data produced by the device (if the virtqueue was set up with
input buffers) or simply to reclaim the buffers if they were already
consumed by the device:
Buffer 회수와 callback 제어
171-188`virtqueue_get_buf_ctx()` 계열은 완료된 buffer를 queue에서 꺼내며 필요하면 함께 저장한 context도 반환합니다.
Virtqueue callback은 `virtqueue_disable_cb()`로 비활성화하고 `virtqueue_enable_cb()` 계열 함수로 다시 활성화할 수 있습니다. 구체적인 variant와 synchronization 동작은 `drivers/virtio/virtio_ring.c`의 kernel-doc을 확인합니다.
특정 scenario에서는 callback을 disable한 뒤에도 spurious callback이 발생할 수 있습니다. Callback을 확실하게 막는 방법은 `virtio_reset_device()`로 device 또는 virtqueue를 reset하는 것입니다.
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_get_buf_ctx
The virtqueue callbacks can be disabled and re-enabled using the
virtqueue_disable_cb() and the family of virtqueue_enable_cb() functions
respectively. See drivers/virtio/virtio_ring.c for more details:
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_disable_cb
.. kernel-doc:: drivers/virtio/virtio_ring.c
:identifiers: virtqueue_enable_cb
But note that some spurious callbacks can still be triggered under
certain scenarios. The way to disable callbacks reliably is to reset the
device or the virtqueue (virtio_reset_device()).
Virtio driver 작성 기준 specification
189-196Driver 구현의 normative reference는 OASIS Virtio Specification v1.2입니다. 실제 개발 시 v1.2만 고정해서 사용하지 말고 이후에 발표된 최신 version도 확인해야 합니다.
References
==========
_`[1]` Virtio Spec v1.2:
https://docs.oasis-open.org/virtio/virtio/v1.2/virtio-v1.2.html
Check for later versions of the spec as well.
요약·해설
writing_virtio_drivers.rst:1-196Virtio driver는 specification과 일치하는 queue를 probe에서 구성하고 DRIVER_OK 뒤 buffer를 제출합니다. Driver가 scatterlist를 queue에 넣고 kick하면 device가 처리 후 callback으로 알리며, remove에서는 reset, unused-buffer 분리, queue 제거 순서로 정리합니다.