From 100c89870c8595f9a2228750a8bc60a5ccded158 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 29 Mar 2021 08:18:08 +0200 Subject: input: console: skip unmappable keys The input console is in charge of turning input events to barebox console keycodes. The keymap array it uses to do so has NR_KEYS entries. Keycodes with values >= NR_KEYS can thus not be mapped. Ignore them instead of evaluating arbitrary memory contents beyond the buffer. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/input/input.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/input/input.c b/drivers/input/input.c index bcc8667417..1df52f56c8 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -165,6 +165,9 @@ static void input_console_notify(struct input_notifier *in, if (ic->modstate[4] || ic->modstate[5]) modstate |= 1 << 2; + if (ev->code >= NR_KEYS) + return; + if (modstate & (1 << 1)) { ascii = keycode_bb_keys[ev->code]; ascii = ascii >= 'a' ? CTL_CH(ascii) : 0; -- cgit v1.2.3 From 1534b57386dce9870054bd488a3ae5907403df38 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 29 Mar 2021 08:18:09 +0200 Subject: input: add virtio input driver We already support Linux event codes, because they are used in the device tree bindings for e.g. gpio-keys. Virtio input devices report events using the same codes, so a driver just has to shovel the codes from virtqueue into the input layer. Do so. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- Documentation/user/virtio.rst | 2 +- drivers/input/Kconfig | 7 ++ drivers/input/Makefile | 1 + drivers/input/virtio_input.c | 191 ++++++++++++++++++++++++++++++++++++++ include/linux/virtio.h | 7 ++ include/linux/virtio_config.h | 62 +++++++++++++ include/linux/virtio_ring.h | 34 +++++++ include/uapi/linux/virtio_input.h | 76 +++++++++++++++ 8 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 drivers/input/virtio_input.c create mode 100644 include/uapi/linux/virtio_input.h diff --git a/Documentation/user/virtio.rst b/Documentation/user/virtio.rst index 7e125c0ca8..fb21869807 100644 --- a/Documentation/user/virtio.rst +++ b/Documentation/user/virtio.rst @@ -35,7 +35,7 @@ queues configuration and buffer transfers are nearly identical. Both MMIO and non-legacy PCI are supported in barebox. The VirtIO spec defines a lots of VirtIO device types, however at present only -block, console and RNG devices are supported. +block, console, input and RNG devices are supported. Build Instructions ------------------ diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 95aa51ebfc..ff3e9d33f6 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -71,4 +71,11 @@ config INPUT_SPECIALKEYS help Say Y here to handle key events like KEY_RESTART and KEY_POWER. +config VIRTIO_INPUT + bool "Virtio input driver" + depends on VIRTIO && BTHREAD + select INPUT + help + This driver supports virtio keyboard input devices. + endmenu diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 36a4204d53..6c8acc6184 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_KEYBOARD_TWL6030) += twl6030_pwrbtn.o obj-$(CONFIG_KEYBOARD_IMX_KEYPAD) += imx_keypad.o obj-$(CONFIG_KEYBOARD_QT1070) += qt1070.o obj-$(CONFIG_INPUT_SPECIALKEYS) += specialkeys.o +obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o diff --git a/drivers/input/virtio_input.c b/drivers/input/virtio_input.c new file mode 100644 index 0000000000..406dc613dc --- /dev/null +++ b/drivers/input/virtio_input.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct virtio_input { + struct input_device idev; + struct virtio_device *vdev; + struct virtqueue *evt; + struct virtio_input_event evts[64]; + struct bthread *bthread; +}; + +static void virtinput_queue_evtbuf(struct virtio_input *vi, + struct virtio_input_event *evtbuf) +{ + struct virtio_sg sg[1]; + virtio_sg_init_one(sg, evtbuf, sizeof(*evtbuf)); + virtqueue_add_inbuf(vi->evt, sg, 1); +} + +static int virtinput_recv_events(struct virtio_input *vi) +{ + struct device_d *dev = &vi->vdev->dev; + struct virtio_input_event *event; + unsigned int len; + int i = 0; + + while ((event = virtqueue_get_buf(vi->evt, &len)) != NULL) { + if (le16_to_cpu(event->type) == EV_KEY) + input_report_key_event(&vi->idev, le16_to_cpu(event->code), + le32_to_cpu(event->value)); + + pr_debug("\n%s: input event #%td received (type=%u, code=%u, value=%u)\n", + dev_name(dev), + event - &vi->evts[0], + le16_to_cpu(event->type), le16_to_cpu(event->code), + le32_to_cpu(event->value)); + + virtinput_queue_evtbuf(vi, event); + i++; + } + + return i; +} + +static int virtinput_poll_vqs(void *_vi) +{ + struct virtio_input *vi = _vi; + + while (!bthread_should_stop()) { + int bufs = 0; + + bufs += virtinput_recv_events(vi); + + if (bufs) + virtqueue_kick(vi->evt); + } + + return 0; +} + +static u8 virtinput_cfg_select(struct virtio_input *vi, + u8 select, u8 subsel) +{ + u8 size; + + virtio_cwrite_le(vi->vdev, struct virtio_input_config, select, &select); + virtio_cwrite_le(vi->vdev, struct virtio_input_config, subsel, &subsel); + virtio_cread_le(vi->vdev, struct virtio_input_config, size, &size); + return size; +} + +static void virtinput_fill_evt(struct virtio_input *vi) +{ + int i, size; + + size = virtqueue_get_vring_size(vi->evt); + if (size > ARRAY_SIZE(vi->evts)) + size = ARRAY_SIZE(vi->evts); + for (i = 0; i < size; i++) + virtinput_queue_evtbuf(vi, &vi->evts[i]); + virtqueue_kick(vi->evt); +} + +static int virtinput_init_vqs(struct virtio_input *vi) +{ + struct virtqueue *vqs[1]; + int err; + + + err = virtio_find_vqs(vi->vdev, 1, vqs); + if (err) + return err; + + vi->evt = vqs[0]; + + return 0; +} + +static int virtinput_probe(struct virtio_device *vdev) +{ + struct virtio_input *vi; + char name[64]; + size_t size; + int err; + + if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) + return -ENODEV; + + vi = kzalloc(sizeof(*vi), GFP_KERNEL); + if (!vi) + return -ENOMEM; + + vdev->priv = vi; + vi->vdev = vdev; + + err = virtinput_init_vqs(vi); + if (err) + goto err_init_vq; + + size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ID_NAME, 0); + virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config, u.string), + name, min(size, sizeof(name))); + name[size] = '\0'; + + virtio_device_ready(vdev); + + err = input_device_register(&vi->idev); + if (err) + goto err_input_register; + + virtinput_fill_evt(vi); + + vi->bthread = bthread_run(virtinput_poll_vqs, vi, + "%s/input0", dev_name(&vdev->dev)); + if (!vi->bthread) { + err = -ENOMEM; + goto err_bthread_run; + } + + dev_info(&vdev->dev, "'%s' probed\n", name); + + return 0; + +err_bthread_run: + bthread_free(vi->bthread); +err_input_register: + vdev->config->del_vqs(vdev); +err_init_vq: + kfree(vi); + return err; +} + +static void virtinput_remove(struct virtio_device *vdev) +{ + struct virtio_input *vi = vdev->priv; + + bthread_stop(vi->bthread); + bthread_free(vi->bthread); + + vdev->config->reset(vdev); + vdev->config->del_vqs(vdev); + kfree(vi); +} + +static const struct virtio_device_id id_table[] = { + { VIRTIO_ID_INPUT, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtio_input_driver = { + .driver.name = "virtio_input", + .id_table = id_table, + .probe = virtinput_probe, + .remove = virtinput_remove, +}; +device_virtio_driver(virtio_input_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Virtio input device driver"); +MODULE_AUTHOR("Gerd Hoffmann "); +MODULE_AUTHOR("Ahmad Fatoum "); diff --git a/include/linux/virtio.h b/include/linux/virtio.h index 8a1a80ddc8..719f45c975 100644 --- a/include/linux/virtio.h +++ b/include/linux/virtio.h @@ -24,6 +24,13 @@ struct virtio_sg { size_t length; }; +static inline void virtio_sg_init_one(struct virtio_sg *sg, + void *addr, size_t length) +{ + sg[0].addr = addr; + sg[0].length = length; +} + struct virtio_config_ops; /** diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h index f33cfdacaa..8160f0952f 100644 --- a/include/linux/virtio_config.h +++ b/include/linux/virtio_config.h @@ -458,6 +458,68 @@ static inline void virtio_cwrite64(struct virtio_device *vdev, _r; \ }) +/* + * Nothing virtio-specific about these, but let's worry about generalizing + * these later. + */ +#define virtio_le_to_cpu(x) \ + _Generic((x), \ + __u8: (u8)(x), \ + __le16: (u16)le16_to_cpu(x), \ + __le32: (u32)le32_to_cpu(x), \ + __le64: (u64)le64_to_cpu(x) \ + ) + +#define virtio_cpu_to_le(x, m) \ + _Generic((m), \ + __u8: (x), \ + __le16: cpu_to_le16(x), \ + __le32: cpu_to_le32(x), \ + __le64: cpu_to_le64(x) \ + ) + +/* LE (e.g. modern) Config space accessors. */ +#define virtio_cread_le(vdev, structname, member, ptr) \ + do { \ + typeof(((structname*)0)->member) virtio_cread_v; \ + \ + /* Sanity check: must match the member's type */ \ + typecheck(typeof(virtio_le_to_cpu(virtio_cread_v)), *(ptr)); \ + \ + switch (sizeof(virtio_cread_v)) { \ + case 1: \ + case 2: \ + case 4: \ + vdev->config->get_config((vdev), \ + offsetof(structname, member), \ + &virtio_cread_v, \ + sizeof(virtio_cread_v)); \ + break; \ + default: \ + __virtio_cread_many((vdev), \ + offsetof(structname, member), \ + &virtio_cread_v, \ + 1, \ + sizeof(virtio_cread_v)); \ + break; \ + } \ + *(ptr) = virtio_le_to_cpu(virtio_cread_v); \ + } while(0) + +#define virtio_cwrite_le(vdev, structname, member, ptr) \ + do { \ + typeof(((structname*)0)->member) virtio_cwrite_v = \ + virtio_cpu_to_le(*(ptr), ((structname*)0)->member); \ + \ + /* Sanity check: must match the member's type */ \ + typecheck(typeof(virtio_le_to_cpu(virtio_cwrite_v)), *(ptr)); \ + \ + vdev->config->set_config((vdev), offsetof(structname, member), \ + &virtio_cwrite_v, \ + sizeof(virtio_cwrite_v)); \ + } while(0) + + #ifdef CONFIG_ARCH_HAS_RESTRICTED_VIRTIO_MEMORY_ACCESS int arch_has_restricted_virtio_memory_access(void); #else diff --git a/include/linux/virtio_ring.h b/include/linux/virtio_ring.h index c349af90ce..bdef47b0fa 100644 --- a/include/linux/virtio_ring.h +++ b/include/linux/virtio_ring.h @@ -180,6 +180,40 @@ struct virtio_sg; int virtqueue_add(struct virtqueue *vq, struct virtio_sg *sgs[], unsigned int out_sgs, unsigned int in_sgs); +/** + * virtqueue_add_outbuf - expose output buffers to other end + * @vq: the struct virtqueue we're talking about. + * @sg: scatterlist (must be well-formed and terminated!) + * @num: the number of entries in @sg readable by other side + * + * Caller must ensure we don't call this with other virtqueue operations + * at the same time (except where noted). + * + * Returns zero or a negative error (ie. ENOSPC, ENOMEM, EIO). + */ +static inline int virtqueue_add_outbuf(struct virtqueue *vq, + struct virtio_sg *sg, unsigned int num) +{ + return virtqueue_add(vq, &sg, num, 0); +} + +/** + * virtqueue_add_inbuf - expose input buffers to other end + * @vq: the struct virtqueue we're talking about. + * @sg: scatterlist (must be well-formed and terminated!) + * @num: the number of entries in @sg writable by other side + * + * Caller must ensure we don't call this with other virtqueue operations + * at the same time (except where noted). + * + * Returns zero or a negative error (ie. ENOSPC, ENOMEM, EIO). + */ +static inline int virtqueue_add_inbuf(struct virtqueue *vq, + struct virtio_sg *sg, unsigned int num) +{ + return virtqueue_add(vq, &sg, 0, num); +} + /** * virtqueue_kick - update after add_buf * diff --git a/include/uapi/linux/virtio_input.h b/include/uapi/linux/virtio_input.h new file mode 100644 index 0000000000..52084b1fb9 --- /dev/null +++ b/include/uapi/linux/virtio_input.h @@ -0,0 +1,76 @@ +#ifndef _LINUX_VIRTIO_INPUT_H +#define _LINUX_VIRTIO_INPUT_H +/* This header is BSD licensed so anyone can use the definitions to implement + * compatible drivers/servers. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of IBM nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. */ + +#include + +enum virtio_input_config_select { + VIRTIO_INPUT_CFG_UNSET = 0x00, + VIRTIO_INPUT_CFG_ID_NAME = 0x01, + VIRTIO_INPUT_CFG_ID_SERIAL = 0x02, + VIRTIO_INPUT_CFG_ID_DEVIDS = 0x03, + VIRTIO_INPUT_CFG_PROP_BITS = 0x10, + VIRTIO_INPUT_CFG_EV_BITS = 0x11, + VIRTIO_INPUT_CFG_ABS_INFO = 0x12, +}; + +struct virtio_input_absinfo { + __le32 min; + __le32 max; + __le32 fuzz; + __le32 flat; + __le32 res; +}; + +struct virtio_input_devids { + __le16 bustype; + __le16 vendor; + __le16 product; + __le16 version; +}; + +struct virtio_input_config { + __u8 select; + __u8 subsel; + __u8 size; + __u8 reserved[5]; + union { + char string[128]; + __u8 bitmap[128]; + struct virtio_input_absinfo abs; + struct virtio_input_devids ids; + } u; +}; + +struct virtio_input_event { + __le16 type; + __le16 code; + __le32 value; +}; + +#endif /* _LINUX_VIRTIO_INPUT_H */ -- cgit v1.2.3 From deb9d7c18b07686b238c511b78179bd330380ddb Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 29 Mar 2021 08:18:10 +0200 Subject: input: virtio_input: add sound support Virtio input devices have an output channel for LEDs and a beeper. Register a sound card for the beeper if sound support is compiled in and the virtualized device reports sound support via the event config bits. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/input/virtio_input.c | 105 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 3 deletions(-) diff --git a/drivers/input/virtio_input.c b/drivers/input/virtio_input.c index 406dc613dc..9c2e4d923f 100644 --- a/drivers/input/virtio_input.c +++ b/drivers/input/virtio_input.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -14,9 +15,11 @@ struct virtio_input { struct input_device idev; struct virtio_device *vdev; - struct virtqueue *evt; + struct virtqueue *evt, *sts; struct virtio_input_event evts[64]; struct bthread *bthread; + struct sound_card beeper; + unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; }; static void virtinput_queue_evtbuf(struct virtio_input *vi, @@ -52,6 +55,51 @@ static int virtinput_recv_events(struct virtio_input *vi) return i; } +/* + * On error we are losing the status update, which isn't critical as + * this is used for the bell. + */ +static int virtinput_send_status(struct sound_card *beeper, unsigned freq, unsigned duration) +{ + struct virtio_input *vi = container_of(beeper, struct virtio_input, beeper); + struct virtio_input_event *stsbuf; + struct virtio_sg sg[1]; + u16 code; + int rc; + + stsbuf = kzalloc(sizeof(*stsbuf), 0); + if (!stsbuf) + return -ENOMEM; + + code = vi->sndbit[0] & BIT_MASK(SND_TONE) ? SND_TONE : SND_BELL; + + stsbuf->type = cpu_to_le16(EV_SND); + stsbuf->code = cpu_to_le16(code); + stsbuf->value = cpu_to_le32(freq); + virtio_sg_init_one(sg, stsbuf, sizeof(*stsbuf)); + + rc = virtqueue_add_outbuf(vi->sts, sg, 1); + virtqueue_kick(vi->sts); + + if (rc != 0) + kfree(stsbuf); + return rc; +} + +static int virtinput_recv_status(struct virtio_input *vi) +{ + struct virtio_input_event *stsbuf; + unsigned int len; + int i = 0; + + while ((stsbuf = virtqueue_get_buf(vi->sts, &len)) != NULL) { + kfree(stsbuf); + i++; + } + + return i; +} + static int virtinput_poll_vqs(void *_vi) { struct virtio_input *vi = _vi; @@ -60,6 +108,7 @@ static int virtinput_poll_vqs(void *_vi) int bufs = 0; bufs += virtinput_recv_events(vi); + bufs += virtinput_recv_status(vi); if (bufs) virtqueue_kick(vi->evt); @@ -79,6 +128,37 @@ static u8 virtinput_cfg_select(struct virtio_input *vi, return size; } +static void virtinput_cfg_bits(struct virtio_input *vi, int select, int subsel, + unsigned long *bits, unsigned int bitcount) +{ + unsigned int bit; + u8 *virtio_bits; + u8 bytes; + + bytes = virtinput_cfg_select(vi, select, subsel); + if (!bytes) + return; + if (bitcount > bytes * 8) + bitcount = bytes * 8; + + /* + * Bitmap in virtio config space is a simple stream of bytes, + * with the first byte carrying bits 0-7, second bits 8-15 and + * so on. + */ + virtio_bits = kzalloc(bytes, GFP_KERNEL); + if (!virtio_bits) + return; + virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config, + u.bitmap), + virtio_bits, bytes); + for (bit = 0; bit < bitcount; bit++) { + if (virtio_bits[bit / 8] & (1 << (bit % 8))) + __set_bit(bit, bits); + } + kfree(virtio_bits); +} + static void virtinput_fill_evt(struct virtio_input *vi) { int i, size; @@ -93,15 +173,16 @@ static void virtinput_fill_evt(struct virtio_input *vi) static int virtinput_init_vqs(struct virtio_input *vi) { - struct virtqueue *vqs[1]; + struct virtqueue *vqs[2]; int err; - err = virtio_find_vqs(vi->vdev, 1, vqs); + err = virtio_find_vqs(vi->vdev, 2, vqs); if (err) return err; vi->evt = vqs[0]; + vi->sts = vqs[1]; return 0; } @@ -132,6 +213,9 @@ static int virtinput_probe(struct virtio_device *vdev) name, min(size, sizeof(name))); name[size] = '\0'; + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_SND, + vi->sndbit, SND_CNT); + virtio_device_ready(vdev); err = input_device_register(&vi->idev); @@ -147,6 +231,21 @@ static int virtinput_probe(struct virtio_device *vdev) goto err_bthread_run; } + if (IS_ENABLED(CONFIG_SOUND) && + (vi->sndbit[0] & (BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE)))) { + struct sound_card *beeper; + + beeper = &vi->beeper; + beeper->name = basprintf("%s/beeper0", dev_name(&vdev->dev)); + beeper->beep = virtinput_send_status; + + err = sound_card_register(&vi->beeper); + if (err) + dev_warn(&vdev->dev, "bell registration failed: %pe\n", ERR_PTR(err)); + else + dev_info(&vdev->dev, "bell registered\n"); + } + dev_info(&vdev->dev, "'%s' probed\n", name); return 0; -- cgit v1.2.3 From e2f7b1d9aca31328499ed4b3df344d96b0ccbd1e Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 1 Apr 2021 08:46:13 +0200 Subject: Documentation: virtio: Fix virtio reference Sphinx complains, probably, because _virtio is used for footnote in the same document. Rename it to fix the reference. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- Documentation/boards/riscv.rst | 2 +- Documentation/user/virtio.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/boards/riscv.rst b/Documentation/boards/riscv.rst index 049029b4f2..59cdc00a99 100644 --- a/Documentation/boards/riscv.rst +++ b/Documentation/boards/riscv.rst @@ -9,7 +9,7 @@ barebox supports both the qemu riscv32 and riscv64 ``-M virt`` boards:: make ARCH=riscv virt64_defconfig qemu-system-riscv64 -M virt -serial stdio -kernel build/images/barebox-dt-2nd.img -Replace ``64`` by ``32`` for 32-bit build. :ref:`virtio` over MMIO is supported and +Replace ``64`` by ``32`` for 32-bit build. :ref:`virtio_sect` over MMIO is supported and can be used for e.g. an extra console or to pass in a virtio-blk device:: qemu-system-riscv64 -M virt -serial stdio \ diff --git a/Documentation/user/virtio.rst b/Documentation/user/virtio.rst index fb21869807..dde47d5f82 100644 --- a/Documentation/user/virtio.rst +++ b/Documentation/user/virtio.rst @@ -4,7 +4,7 @@ Copyright (C) 2018, Bin Meng Copyright (C) 2021, Ahmad Fatoum -.. _virtio: +.. _virtio_sect: VirtIO Support ============== -- cgit v1.2.3