Hello,
This is an initial implementation of a generic vhost-user backend for the I2C bus. This is based of the virtio specifications (already merged) for the I2C bus.
The kernel virtio I2C driver is still under review, here is the latest version (v10):
https://lore.kernel.org/lkml/226a8d5663b7bb6f5d06ede7701eedb18d1bafa1.161649...
The backend is implemented as a vhost-user device because we want to experiment in making portable backends that can be used with multiple hypervisors. We also want to support backends isolated in their own separate service VMs with limited memory cross-sections with the principle guest. This is part of a wider initiative by Linaro called "project Stratos" for which you can find information here:
https://collaborate.linaro.org/display/STR/Stratos+Home
I mentioned this to explain the decision to write the daemon as a fairly pure glib application that just depends on libvhost-user.
We are not sure where the vhost-user backend should get queued, qemu or a separate repository. Similar questions were raised by an earlier thread by Alex Bennée for his RPMB work:
https://lore.kernel.org/qemu-devel/20200925125147.26943-1-alex.bennee@linaro...
I2C Testing: ------------
I didn't have access to a real hardware where I can play with a I2C client device (like RTC, eeprom, etc) to verify the working of the backend daemon, so I decided to test it on my x86 box itself with hierarchy of two ARM64 guests.
The first ARM64 guest was passed "-device ds1338,address=0x20" option, so it could emulate a ds1338 RTC device, which connects to an I2C bus. Once the guest came up, ds1338 device instance was created within the guest kernel by doing:
echo ds1338 0x20 > /sys/bus/i2c/devices/i2c-0/new_device
[ Note that this may end up binding the ds1338 device to its driver, which won't let our i2c daemon talk to the device. For that we need to manually unbind the device from the driver:
echo 0-0020 > /sys/bus/i2c/devices/0-0020/driver/unbind ]
After this is done, you will get /dev/rtc1. This is the device we wanted to emulate, which will be accessed by the vhost-user-i2c backend daemon via the /dev/i2c-0 file present in the guest VM.
At this point we need to start the backend daemon and give it a socket-path to talk to from qemu (you can pass -v to it to get more detailed messages):
vhost-user-i2c --socket-path=vi2c.sock --device-list 0:20
[ Here, 0:20 is the bus/device mapping, 0 for /dev/i2c-0 and 20 is client address of ds1338 that we used while creating the device. ]
Now we need to start the second level ARM64 guest (from within the first guest) to get the i2c-virtio.c Linux driver up. The second level guest is passed the following options to connect to the same socket:
-chardev socket,path=vi2c.sock,id=vi2c \ -device vhost-user-i2c-pci,chardev=vi2c,id=i2c
Once the second level guest boots up, we will see the i2c-virtio bus at /sys/bus/i2c/devices/i2c-X/. From there we can now make it emulate the ds1338 device again by doing:
echo ds1338 0x20 > /sys/bus/i2c/devices/i2c-0/new_device
[ This time we want ds1338's driver to be bound to the device, so it should be enabled in the kernel as well. ]
And we will get /dev/rtc1 device again here in the second level guest. Now we can play with the rtc device with help of hwclock utility and we can see the following sequence of transfers happening if we try to update rtc's time from system time.
hwclock -w -f /dev/rtc1 (in guest2) -> Reaches i2c-virtio.c (Linux bus driver in guest2) -> transfer over virtio -> Reaches the qemu's vhost-i2c device emulation (running over guest1) -> Reaches the backend daemon vhost-user-i2c started earlier (in guest1) -> ioctl(/dev/i2c-0, I2C_RDWR, ..); (in guest1) -> reaches qemu's hw/rtc/ds1338.c (running over host)
SMBUS Testing: --------------
I wasn't required to have such a tedious setup for testing out with SMBUS devices. I was able to emulate a SMBUS device on my x86 machine using i2c-stub driver.
$ modprobe i2c-stub chip_addr=0x20 //Boot the arm64 guest now with i2c-virtio driver and then do: $ echo al3320a 0x20 > /sys/class/i2c-adapter/i2c-0/new_device $ cat /sys/bus/iio/devices/iio:device0/in_illuminance_raw
That's it.
I hope I was able to give a clear picture of my test setup here :)
Thanks.
V1->V2: - Add a separate patch (not to be merged) to keep stuff temporarily taken from Linux kernel.
- Support SMBUS devices/busses in the backend daemon.
- Fix lots of checkpatch warnings/errors.
- Some other bug fixes, suggestions, etc.
Viresh Kumar (6): !Merge: Update virtio headers from kernel hw/virtio: add boilerplate for vhost-user-i2c device hw/virtio: add vhost-user-i2c-pci boilerplate tools/vhost-user-i2c: Add backend driver docs: add a man page for vhost-user-i2c MAINTAINERS: Add entry for virtio-i2c
MAINTAINERS | 9 + docs/tools/index.rst | 1 + docs/tools/vhost-user-i2c.rst | 75 ++ hw/virtio/Kconfig | 5 + hw/virtio/meson.build | 2 + hw/virtio/vhost-user-i2c-pci.c | 69 ++ hw/virtio/vhost-user-i2c.c | 285 +++++++ include/hw/virtio/vhost-user-i2c.h | 37 + include/standard-headers/linux/virtio_i2c.h | 40 + include/standard-headers/linux/virtio_ids.h | 1 + tools/meson.build | 8 + tools/vhost-user-i2c/50-qemu-i2c.json.in | 5 + tools/vhost-user-i2c/main.c | 809 ++++++++++++++++++++ tools/vhost-user-i2c/meson.build | 10 + 14 files changed, 1356 insertions(+) create mode 100644 docs/tools/vhost-user-i2c.rst create mode 100644 hw/virtio/vhost-user-i2c-pci.c create mode 100644 hw/virtio/vhost-user-i2c.c create mode 100644 include/hw/virtio/vhost-user-i2c.h create mode 100644 include/standard-headers/linux/virtio_i2c.h create mode 100644 tools/vhost-user-i2c/50-qemu-i2c.json.in create mode 100644 tools/vhost-user-i2c/main.c create mode 100644 tools/vhost-user-i2c/meson.build
DO NOT MERGE
Update Linux virtio headers with help of
./scripts/update-linux-headers.sh ~/lsrc/linux.git
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- include/standard-headers/linux/virtio_i2c.h | 40 +++++++++++++++++++++ include/standard-headers/linux/virtio_ids.h | 1 + 2 files changed, 41 insertions(+) create mode 100644 include/standard-headers/linux/virtio_i2c.h
diff --git a/include/standard-headers/linux/virtio_i2c.h b/include/standard-headers/linux/virtio_i2c.h new file mode 100644 index 000000000000..378d31c4a527 --- /dev/null +++ b/include/standard-headers/linux/virtio_i2c.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ +/* + * Definitions for virtio I2C Adpter + * + * Copyright (c) 2021 Intel Corporation. All rights reserved. + */ + +#ifndef _LINUX_VIRTIO_I2C_H +#define _LINUX_VIRTIO_I2C_H + +#include "standard-headers/linux/types.h" + +/* The bit 0 of the @virtio_i2c_out_hdr.@flags, used to group the requests */ +#define VIRTIO_I2C_FLAGS_FAIL_NEXT 0x00000001 + +/** + * struct virtio_i2c_out_hdr - the virtio I2C message OUT header + * @addr: the controlled device address + * @padding: used to pad to full dword + * @flags: used for feature extensibility + */ +struct virtio_i2c_out_hdr { + uint16_t addr; + uint16_t padding; + uint32_t flags; +}; + +/** + * struct virtio_i2c_in_hdr - the virtio I2C message IN header + * @status: the processing result from the backend + */ +struct virtio_i2c_in_hdr { + uint8_t status; +}; + +/* The final status written by the device */ +#define VIRTIO_I2C_MSG_OK 0 +#define VIRTIO_I2C_MSG_ERR 1 + +#endif /* _LINUX_VIRTIO_I2C_H */ diff --git a/include/standard-headers/linux/virtio_ids.h b/include/standard-headers/linux/virtio_ids.h index bc1c0621f5ed..b89391dff6c9 100644 --- a/include/standard-headers/linux/virtio_ids.h +++ b/include/standard-headers/linux/virtio_ids.h @@ -54,5 +54,6 @@ #define VIRTIO_ID_FS 26 /* virtio filesystem */ #define VIRTIO_ID_PMEM 27 /* virtio pmem */ #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */ +#define VIRTIO_ID_I2C_ADAPTER 34 /* virtio i2c adapter */
#endif /* _LINUX_VIRTIO_IDS_H */
This creates the QEMU side of the vhost-user-i2c device which connects to the remote daemon. It is based of vhost-user-fs code.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- hw/virtio/Kconfig | 5 + hw/virtio/meson.build | 1 + hw/virtio/vhost-user-i2c.c | 285 +++++++++++++++++++++++++++++ include/hw/virtio/vhost-user-i2c.h | 37 ++++ 4 files changed, 328 insertions(+) create mode 100644 hw/virtio/vhost-user-i2c.c create mode 100644 include/hw/virtio/vhost-user-i2c.h
diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig index 0eda25c4e1bf..35ab45e2095c 100644 --- a/hw/virtio/Kconfig +++ b/hw/virtio/Kconfig @@ -58,3 +58,8 @@ config VIRTIO_MEM depends on LINUX depends on VIRTIO_MEM_SUPPORTED select MEM_DEVICE + +config VHOST_USER_I2C + bool + default y + depends on VIRTIO && VHOST_USER diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build index fbff9bc9d4de..1a0d736a0db5 100644 --- a/hw/virtio/meson.build +++ b/hw/virtio/meson.build @@ -25,6 +25,7 @@ virtio_ss.add(when: 'CONFIG_VHOST_USER_VSOCK', if_true: files('vhost-user-vsock. virtio_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng.c')) virtio_ss.add(when: 'CONFIG_VIRTIO_IOMMU', if_true: files('virtio-iommu.c')) virtio_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true: files('virtio-mem.c')) +virtio_ss.add(when: 'CONFIG_VHOST_USER_I2C', if_true: files('vhost-user-i2c.c'))
virtio_pci_ss = ss.source_set() virtio_pci_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock-pci.c')) diff --git a/hw/virtio/vhost-user-i2c.c b/hw/virtio/vhost-user-i2c.c new file mode 100644 index 000000000000..20a49bc0078b --- /dev/null +++ b/hw/virtio/vhost-user-i2c.c @@ -0,0 +1,285 @@ +/* + * Vhost-user i2c virtio device + * + * Copyright (c) 2021 Viresh Kumar viresh.kumar@linaro.org + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/qdev-properties.h" +#include "hw/virtio/virtio-bus.h" +#include "hw/virtio/vhost-user-i2c.h" +#include "qemu/error-report.h" +#include "standard-headers/linux/virtio_ids.h" + +static void vu_i2c_start(VirtIODevice *vdev) +{ + VHostUserI2C *i2c = VHOST_USER_I2C(vdev); + BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); + VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); + int ret; + int i; + + if (!k->set_guest_notifiers) { + error_report("binding does not support guest notifiers"); + return; + } + + ret = vhost_dev_enable_notifiers(&i2c->vhost_dev, vdev); + if (ret < 0) { + error_report("Error enabling host notifiers: %d", -ret); + return; + } + + ret = k->set_guest_notifiers(qbus->parent, i2c->vhost_dev.nvqs, true); + if (ret < 0) { + error_report("Error binding guest notifier: %d", -ret); + goto err_host_notifiers; + } + + i2c->vhost_dev.acked_features = vdev->guest_features; + ret = vhost_dev_start(&i2c->vhost_dev, vdev); + if (ret < 0) { + error_report("Error starting vhost-user-i2c: %d", -ret); + goto err_guest_notifiers; + } + + /* + * guest_notifier_mask/pending not used yet, so just unmask + * everything here. virtio-pci will do the right thing by + * enabling/disabling irqfd. + */ + for (i = 0; i < i2c->vhost_dev.nvqs; i++) { + vhost_virtqueue_mask(&i2c->vhost_dev, vdev, i, false); + } + + return; + +err_guest_notifiers: + k->set_guest_notifiers(qbus->parent, i2c->vhost_dev.nvqs, false); +err_host_notifiers: + vhost_dev_disable_notifiers(&i2c->vhost_dev, vdev); +} + +static void vu_i2c_stop(VirtIODevice *vdev) +{ + VHostUserI2C *i2c = VHOST_USER_I2C(vdev); + BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); + VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); + int ret; + + if (!k->set_guest_notifiers) { + return; + } + + vhost_dev_stop(&i2c->vhost_dev, vdev); + + ret = k->set_guest_notifiers(qbus->parent, i2c->vhost_dev.nvqs, false); + if (ret < 0) { + error_report("vhost guest notifier cleanup failed: %d", ret); + return; + } + + vhost_dev_disable_notifiers(&i2c->vhost_dev, vdev); +} + +static void vu_i2c_set_status(VirtIODevice *vdev, uint8_t status) +{ + VHostUserI2C *i2c = VHOST_USER_I2C(vdev); + bool should_start = status & VIRTIO_CONFIG_S_DRIVER_OK; + + if (!vdev->vm_running) { + should_start = false; + } + + if (i2c->vhost_dev.started == should_start) { + return; + } + + if (should_start) { + vu_i2c_start(vdev); + } else { + vu_i2c_stop(vdev); + } +} + +static uint64_t vu_i2c_get_features(VirtIODevice *vdev, + uint64_t requested_features, Error **errp) +{ + /* No feature bits used yet */ + return requested_features; +} + +static void vu_i2c_handle_output(VirtIODevice *vdev, VirtQueue *vq) +{ + /* + * Not normally called; it's the daemon that handles the queue; + * however virtio's cleanup path can call this. + */ +} + +static void vu_i2c_guest_notifier_mask(VirtIODevice *vdev, int idx, bool mask) +{ + VHostUserI2C *i2c = VHOST_USER_I2C(vdev); + + vhost_virtqueue_mask(&i2c->vhost_dev, vdev, idx, mask); +} + +static bool vu_i2c_guest_notifier_pending(VirtIODevice *vdev, int idx) +{ + VHostUserI2C *i2c = VHOST_USER_I2C(vdev); + + return vhost_virtqueue_pending(&i2c->vhost_dev, idx); +} + +static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserI2C *i2c) +{ + vhost_user_cleanup(&i2c->vhost_user); + virtio_delete_queue(i2c->req_vq); + virtio_cleanup(vdev); + g_free(i2c->vhost_dev.vqs); + i2c->vhost_dev.vqs = NULL; +} + +static int vu_i2c_connect(DeviceState *dev) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VHostUserI2C *i2c = VHOST_USER_I2C(vdev); + + if (i2c->connected) { + return 0; + } + i2c->connected = true; + + /* restore vhost state */ + if (virtio_device_started(vdev, vdev->status)) { + vu_i2c_start(vdev); + } + + return 0; +} + +static void vu_i2c_disconnect(DeviceState *dev) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VHostUserI2C *i2c = VHOST_USER_I2C(vdev); + + if (!i2c->connected) { + return; + } + i2c->connected = false; + + if (i2c->vhost_dev.started) { + vu_i2c_stop(vdev); + } +} + +static void vu_i2c_event(void *opaque, QEMUChrEvent event) +{ + DeviceState *dev = opaque; + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VHostUserI2C *i2c = VHOST_USER_I2C(vdev); + + switch (event) { + case CHR_EVENT_OPENED: + if (vu_i2c_connect(dev) < 0) { + qemu_chr_fe_disconnect(&i2c->conf.chardev); + return; + } + break; + case CHR_EVENT_CLOSED: + vu_i2c_disconnect(dev); + break; + case CHR_EVENT_BREAK: + case CHR_EVENT_MUX_IN: + case CHR_EVENT_MUX_OUT: + /* Ignore */ + break; + } +} + +static void vu_i2c_device_realize(DeviceState *dev, Error **errp) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VHostUserI2C *i2c = VHOST_USER_I2C(dev); + int ret; + + if (!i2c->conf.chardev.chr) { + error_setg(errp, "missing chardev"); + return; + } + + if (!vhost_user_init(&i2c->vhost_user, &i2c->conf.chardev, errp)) { + return; + } + + virtio_init(vdev, "vhost-user-i2c", VIRTIO_ID_I2C_ADAPTER, 0); + + i2c->req_vq = virtio_add_queue(vdev, 4, vu_i2c_handle_output); + i2c->vhost_dev.nvqs = 1; + i2c->vhost_dev.vqs = g_new0(struct vhost_virtqueue, i2c->vhost_dev.nvqs); + ret = vhost_dev_init(&i2c->vhost_dev, &i2c->vhost_user, + VHOST_BACKEND_TYPE_USER, 0); + if (ret < 0) { + error_setg_errno(errp, -ret, "vhost_dev_init() failed"); + do_vhost_user_cleanup(vdev, i2c); + } + + qemu_chr_fe_set_handlers(&i2c->conf.chardev, NULL, NULL, vu_i2c_event, NULL, + dev, NULL, true); +} + +static void vu_i2c_device_unrealize(DeviceState *dev) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VHostUserI2C *i2c = VHOST_USER_I2C(dev); + + /* This will stop vhost backend if appropriate. */ + vu_i2c_set_status(vdev, 0); + + vhost_dev_cleanup(&i2c->vhost_dev); + + do_vhost_user_cleanup(vdev, i2c); +} + +static const VMStateDescription vu_i2c_vmstate = { + .name = "vhost-user-i2c", + .unmigratable = 1, +}; + +static Property vu_i2c_properties[] = { + DEFINE_PROP_CHR("chardev", VHostUserI2C, conf.chardev), + DEFINE_PROP_END_OF_LIST(), +}; + +static void vu_i2c_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + + device_class_set_props(dc, vu_i2c_properties); + dc->vmsd = &vu_i2c_vmstate; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + vdc->realize = vu_i2c_device_realize; + vdc->unrealize = vu_i2c_device_unrealize; + vdc->get_features = vu_i2c_get_features; + vdc->set_status = vu_i2c_set_status; + vdc->guest_notifier_mask = vu_i2c_guest_notifier_mask; + vdc->guest_notifier_pending = vu_i2c_guest_notifier_pending; +} + +static const TypeInfo vu_i2c_info = { + .name = TYPE_VHOST_USER_I2C, + .parent = TYPE_VIRTIO_DEVICE, + .instance_size = sizeof(VHostUserI2C), + .class_init = vu_i2c_class_init, +}; + +static void vu_i2c_register_types(void) +{ + type_register_static(&vu_i2c_info); +} + +type_init(vu_i2c_register_types) diff --git a/include/hw/virtio/vhost-user-i2c.h b/include/hw/virtio/vhost-user-i2c.h new file mode 100644 index 000000000000..a5fffcb6096c --- /dev/null +++ b/include/hw/virtio/vhost-user-i2c.h @@ -0,0 +1,37 @@ +/* + * Vhost-user i2c virtio device + * + * Copyright (c) 2021 Viresh Kumar viresh.kumar@linaro.org + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _QEMU_VHOST_USER_I2C_H +#define _QEMU_VHOST_USER_I2C_H + +#include "hw/virtio/virtio.h" +#include "hw/virtio/vhost.h" +#include "hw/virtio/vhost-user.h" +#include "chardev/char-fe.h" + +#define TYPE_VHOST_USER_I2C "vhost-user-i2c-device" +OBJECT_DECLARE_SIMPLE_TYPE(VHostUserI2C, VHOST_USER_I2C) + +typedef struct { + CharBackend chardev; +} VHostUserI2CConf; + +struct VHostUserI2C { + /*< private >*/ + VirtIODevice parent; + VHostUserI2CConf conf; + struct vhost_virtqueue *vhost_vq; + struct vhost_dev vhost_dev; + VhostUserState vhost_user; + VirtQueue *req_vq; + bool connected; + + /*< public >*/ +}; + +#endif /* _QEMU_VHOST_USER_I2C_H */
Viresh Kumar viresh.kumar@linaro.org writes:
This creates the QEMU side of the vhost-user-i2c device which connects to the remote daemon. It is based of vhost-user-fs code.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Alex Bennée alex.bennee@linaro.org
This allows is to instantiate a vhost-user-i2c device as part of a PCI bus. It is mostly boilerplate which looks pretty similar to the vhost-user-fs-pci device.
Reviewed-by: Alex Bennée alex.bennee@linaro.org Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- hw/virtio/meson.build | 1 + hw/virtio/vhost-user-i2c-pci.c | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 hw/virtio/vhost-user-i2c-pci.c
diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build index 1a0d736a0db5..bc352a600911 100644 --- a/hw/virtio/meson.build +++ b/hw/virtio/meson.build @@ -26,6 +26,7 @@ virtio_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng.c')) virtio_ss.add(when: 'CONFIG_VIRTIO_IOMMU', if_true: files('virtio-iommu.c')) virtio_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true: files('virtio-mem.c')) virtio_ss.add(when: 'CONFIG_VHOST_USER_I2C', if_true: files('vhost-user-i2c.c')) +virtio_ss.add(when: ['CONFIG_VIRTIO_PCI', 'CONFIG_VHOST_USER_I2C'], if_true: files('vhost-user-i2c-pci.c'))
virtio_pci_ss = ss.source_set() virtio_pci_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock-pci.c')) diff --git a/hw/virtio/vhost-user-i2c-pci.c b/hw/virtio/vhost-user-i2c-pci.c new file mode 100644 index 000000000000..70b7b65fd970 --- /dev/null +++ b/hw/virtio/vhost-user-i2c-pci.c @@ -0,0 +1,69 @@ +/* + * Vhost-user i2c virtio device PCI glue + * + * Copyright (c) 2021 Viresh Kumar viresh.kumar@linaro.org + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/qdev-properties.h" +#include "hw/virtio/vhost-user-i2c.h" +#include "virtio-pci.h" + +struct VHostUserI2CPCI { + VirtIOPCIProxy parent_obj; + VHostUserI2C vdev; +}; + +typedef struct VHostUserI2CPCI VHostUserI2CPCI; + +#define TYPE_VHOST_USER_I2C_PCI "vhost-user-i2c-pci-base" + +DECLARE_INSTANCE_CHECKER(VHostUserI2CPCI, VHOST_USER_I2C_PCI, + TYPE_VHOST_USER_I2C_PCI) + +static void vhost_user_i2c_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) +{ + VHostUserI2CPCI *dev = VHOST_USER_I2C_PCI(vpci_dev); + DeviceState *vdev = DEVICE(&dev->vdev); + + vpci_dev->nvectors = 1; + qdev_realize(vdev, BUS(&vpci_dev->bus), errp); +} + +static void vhost_user_i2c_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); + PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); + k->realize = vhost_user_i2c_pci_realize; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; + pcidev_k->device_id = 0; /* Set by virtio-pci based on virtio id */ + pcidev_k->revision = 0x00; + pcidev_k->class_id = PCI_CLASS_COMMUNICATION_OTHER; +} + +static void vhost_user_i2c_pci_instance_init(Object *obj) +{ + VHostUserI2CPCI *dev = VHOST_USER_I2C_PCI(obj); + + virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev), + TYPE_VHOST_USER_I2C); +} + +static const VirtioPCIDeviceTypeInfo vhost_user_i2c_pci_info = { + .base_name = TYPE_VHOST_USER_I2C_PCI, + .non_transitional_name = "vhost-user-i2c-pci", + .instance_size = sizeof(VHostUserI2CPCI), + .instance_init = vhost_user_i2c_pci_instance_init, + .class_init = vhost_user_i2c_pci_class_init, +}; + +static void vhost_user_i2c_pci_register(void) +{ + virtio_pci_types_register(&vhost_user_i2c_pci_info); +} + +type_init(vhost_user_i2c_pci_register);
This adds the vhost-user backend driver to support virtio based I2C and SMBUS devices.
vhost-user-i2c --help
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- tools/meson.build | 8 + tools/vhost-user-i2c/50-qemu-i2c.json.in | 5 + tools/vhost-user-i2c/main.c | 809 +++++++++++++++++++++++ tools/vhost-user-i2c/meson.build | 10 + 4 files changed, 832 insertions(+) create mode 100644 tools/vhost-user-i2c/50-qemu-i2c.json.in create mode 100644 tools/vhost-user-i2c/main.c create mode 100644 tools/vhost-user-i2c/meson.build
diff --git a/tools/meson.build b/tools/meson.build index 3e5a0abfa29f..8271e110978b 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -24,3 +24,11 @@ endif if have_virtiofsd subdir('virtiofsd') endif + +have_virtioi2c= (have_system and + have_tools and + 'CONFIG_LINUX' in config_host) + +if have_virtioi2c + subdir('vhost-user-i2c') +endif diff --git a/tools/vhost-user-i2c/50-qemu-i2c.json.in b/tools/vhost-user-i2c/50-qemu-i2c.json.in new file mode 100644 index 000000000000..dafd1337fa9c --- /dev/null +++ b/tools/vhost-user-i2c/50-qemu-i2c.json.in @@ -0,0 +1,5 @@ +{ + "description": "QEMU vhost-user-i2c", + "type": "bridge", + "binary": "@libexecdir@/vhost-user-i2c" +} diff --git a/tools/vhost-user-i2c/main.c b/tools/vhost-user-i2c/main.c new file mode 100644 index 000000000000..2e0a17dd6ed6 --- /dev/null +++ b/tools/vhost-user-i2c/main.c @@ -0,0 +1,809 @@ +/* + * VIRTIO I2C Emulation via vhost-user + * + * Copyright (c) 2021 Viresh Kumar viresh.kumar@linaro.org + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define G_LOG_DOMAIN "vhost-user-i2c" +#define G_LOG_USE_STRUCTURED 1 + +#include <assert.h> +#include <endian.h> +#include <fcntl.h> +#include <gio/gio.h> +#include <gio/gunixsocketaddress.h> +#include <glib.h> +#include <glib-unix.h> +#include <glib/gstdio.h> +#include <inttypes.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "qemu/compiler.h" +#include "qemu/cutils.h" +#include "standard-headers/linux/virtio_i2c.h" +#include "subprojects/libvhost-user/libvhost-user-glib.h" +#include "subprojects/libvhost-user/libvhost-user.h" + +#define VHOST_USER_I2C_MAX_QUEUES 1 + +/* vhost-user-i2c definitions */ + +#define MAX_I2C_VDEV (1 << 7) +#define MAX_I2C_ADAPTER 16 + +typedef struct { + int32_t fd; + int32_t bus; + bool smbus; + bool clients[MAX_I2C_VDEV]; +} VI2cAdapter; + +typedef struct { + VugDev dev; + GMainLoop *loop; + VI2cAdapter *adapter[MAX_I2C_ADAPTER]; + uint16_t adapter_map[MAX_I2C_VDEV]; + uint32_t adapter_num; +} VuI2c; + +static gboolean print_cap, verbose; +static gchar *socket_path, *device_list; +static gint socket_fd = -1; + +static GOptionEntry options[] = { + { "socket-path", 's', 0, G_OPTION_ARG_FILENAME, &socket_path, + "Location of vhost-user Unix domain socket, incompatible with --fd", + "PATH" }, + { "fd", 'f', 0, G_OPTION_ARG_INT, &socket_fd, + "Specify file-descriptor of the backend, don't use with --socket-path", + "FD" }, + { "device-list", 'l', 0, G_OPTION_ARG_STRING, &device_list, + "List of i2c-dev bus and attached devices", "I2C Devices" }, + { "print-capabilities", 'c', 0, G_OPTION_ARG_NONE, &print_cap, + "Output to stdout the backend capabilities in JSON format and exit", + NULL}, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "Be more verbose in output", NULL}, + { NULL } +}; + + +/* I2c helpers */ +static void fmt_bytes(GString *s, uint8_t *bytes, int len) +{ + int32_t i; + for (i = 0; i < len; i++) { + if (i && i % 16 == 0) { + g_string_append_c(s, '\n'); + } + g_string_append_printf(s, "%x ", bytes[i]); + } +} + +static void vi2c_dump_msg(struct i2c_msg *msgs, size_t count) +{ + int32_t i; + + for (i = 0; i < count; i++) { + g_autoptr(GString) s = g_string_new("\nI2c request: "); + + g_string_append_printf(s, "addr: %x\n", msgs[i].addr); + g_string_append_printf(s, "transfer len: %x\n", msgs[i].len); + + g_string_append_printf(s, "%s: ", msgs[i].flags & I2C_M_RD ? + "Data read" : "Data Written"); + fmt_bytes(s, (uint8_t *)msgs[i].buf, msgs[i].len); + g_string_append_printf(s, "\n"); + + g_debug("%s: %s", __func__, s->str); + } +} + +static int vi2c_map_adapters(VuI2c *i2c) +{ + VI2cAdapter *adapter; + int32_t i, client_addr; + + /* + * Flatten the map for client address and adapter to the array: + * + * adapter_map[MAX_I2C_VDEV]: + * + * Adapter | adapter2 | none | adapter1 | adapter3 | none| (val) + * |----------|-------|----------|----------|-----| + * Slave Address | addr 1 | none | addr 2 | addr 3 | none| (idx) + * |<-----------------------MAX_I2C_VDEV---------------->| + */ + for (i = 0; i < i2c->adapter_num; i++) { + adapter = i2c->adapter[i]; + + for (client_addr = 0; client_addr < MAX_I2C_VDEV; client_addr++) { + if (adapter->clients[client_addr]) { + if (i2c->adapter_map[client_addr]) { + g_printerr("client addr %x repeated, not supported!\n", + client_addr); + return -1; + } + + /* The array is initialized to 0, + 1 for index */ + i2c->adapter_map[client_addr] = i + 1; + if (verbose) { + g_print("client: 0x%x -> i2c adapter: %d\n", client_addr, + adapter->bus); + } + } + } + } + return 0; +} + +static VI2cAdapter *vi2c_find_adapter(VuI2c *i2c, uint16_t addr) +{ + if (addr < MAX_I2C_VDEV && (i2c->adapter_map[addr] != 0)) { + return i2c->adapter[i2c->adapter_map[addr] - 1]; + } + + return NULL; +} + +static bool vi2c_set_client_addr(VI2cAdapter *adapter, uint16_t addr) +{ + if (ioctl(adapter->fd, I2C_SLAVE, addr) < 0) { + if (errno == EBUSY) { + g_printerr("client device %x is busy!\n", addr); + } else { + g_printerr("client device %d does not exist!\n", addr); + } + return false; + } + return true; +} + +static void vi2c_remove_adapters(VuI2c *i2c) +{ + VI2cAdapter *adapter; + int32_t i; + + for (i = 0; i < MAX_I2C_ADAPTER; i++) { + adapter = i2c->adapter[i]; + if (!adapter) { + break; + } + + if (adapter->fd > 0) { + close(adapter->fd); + } + + g_free(adapter); + i2c->adapter[i] = NULL; + } +} + +static VI2cAdapter *vi2c_create_adapter(int32_t bus, uint16_t client_addr[], + int32_t n_client) +{ + VI2cAdapter *adapter; + char path[20]; + uint64_t funcs; + int32_t fd, i; + + if (bus < 0) { + return NULL; + } + + adapter = g_malloc0(sizeof(*adapter)); + if (!adapter) { + g_printerr("failed to alloc adapter"); + return NULL; + } + + snprintf(path, sizeof(path), "/dev/i2c-%d", bus); + path[sizeof(path) - 1] = '\0'; + + fd = open(path, O_RDWR); + if (fd < 0) { + g_printerr("virtio_i2c: failed to open %s\n", path); + goto fail; + } + + adapter->fd = fd; + adapter->bus = bus; + + if (ioctl(fd, I2C_FUNCS, &funcs) < 0) { + g_printerr("virtio_i2c: failed to get functionality %s: %d\n", path, + errno); + goto close_fd; + } + + if (funcs & I2C_FUNC_I2C) { + adapter->smbus = false; + } else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) { + adapter->smbus = true; + } else { + g_printerr("virtio_i2c: invalid functionality %lx\n", funcs); + goto close_fd; + } + + for (i = 0; i < n_client; i++) { + if (client_addr[i]) { + if (!vi2c_set_client_addr(adapter, client_addr[i])) { + goto close_fd; + } + + if (adapter->clients[client_addr[i]]) { + g_printerr("client addr 0x%x repeat, not allowed.\n", + client_addr[i]); + goto close_fd; + } + + adapter->clients[client_addr[i]] = true; + if (verbose) { + g_print("Added client 0x%x to bus %u\n", client_addr[i], bus); + } + } + } + + if (verbose) { + g_print("Added adapter: bus: %d, func %s\n", bus, + adapter->smbus ? "smbus" : "i2c"); + } + return adapter; + +close_fd: + close(fd); +fail: + g_free(adapter); + return NULL; +} + +/* + * Virtio I2C device list format. + * + * <bus>:<client_addr>[:<client_addr>], + * [<bus>:<client_addr>[:<client_addr>]] + * + * bus (dec): adatper bus number. + * e.g. 2 for /dev/i2c-2 + * client_addr (hex): address for client device + * e.g. 0x1C or 1C + * + * Example: --device-list="2:0x1c:0x20,3:0x10:0x2c" + * + * Note: client address can not repeat. + */ +static int32_t vi2c_parse(VuI2c *i2c) +{ + uint16_t client_addr[MAX_I2C_VDEV]; + int32_t n_adapter = 0, n_client; + int64_t addr, bus; + const char *cp, *t; + + while (device_list) { + /* Read <bus>:<client_addr>[:<client_addr>] entries one by one */ + cp = strsep(&device_list, ","); + + if (!cp || *cp == '\0') { + break; + } + + if (n_adapter == MAX_I2C_ADAPTER) { + g_printerr("too many adapter (%d), only support %d\n", n_adapter, + MAX_I2C_ADAPTER); + goto out; + } + + if (qemu_strtol(cp, &t, 10, &bus) || bus < 0) { + g_printerr("Invalid bus number %s\n", cp); + goto out; + } + + cp = t; + n_client = 0; + + /* Parse clients <client_addr>[:<client_addr>] entries one by one */ + while (cp != NULL && *cp != '\0') { + if (*cp == ':') { + cp++; + } + + if (n_client == MAX_I2C_VDEV) { + g_printerr("too many devices (%d), only support %d\n", + n_client, MAX_I2C_VDEV); + goto out; + } + + if (qemu_strtol(cp, &t, 16, &addr) || + addr < 0 || addr > MAX_I2C_VDEV) { + g_printerr("Invalid address %s : %lx\n", cp, addr); + goto out; + } + + client_addr[n_client++] = addr; + cp = t; + if (verbose) { + g_print("i2c adapter %ld:0x%lx\n", bus, addr); + } + } + + i2c->adapter[n_adapter] = vi2c_create_adapter(bus, client_addr, + n_client); + if (!i2c->adapter[n_adapter]) { + goto out; + } + + n_adapter++; + } + + if (!n_adapter) { + g_printerr("Failed to add any adapters\n"); + return -1; + } + + i2c->adapter_num = n_adapter; + + if (!vi2c_map_adapters(i2c)) { + return 0; + } + +out: + vi2c_remove_adapters(i2c); + return -1; +} + +static int32_t i2c_xfer(VI2cAdapter *adapter, struct i2c_msg *msgs, + size_t count) +{ + struct i2c_rdwr_ioctl_data data; + + data.nmsgs = count; + data.msgs = msgs; + + return ioctl(adapter->fd, I2C_RDWR, &data); +} + +/* + * Based on Linux's drivers/i2c/i2c-core-smbus.c:i2c_smbus_xfer_emulated(). This + * function tries to reverse what Linux does, only support basic modes (up to + * word transfer). + */ +static int32_t smbus_xfer(VI2cAdapter *adapter, struct i2c_msg *msgs, + size_t count) +{ + union i2c_smbus_data data; + struct i2c_smbus_ioctl_data smbus_data = { .data = &data }; + int32_t ret; + + smbus_data.command = msgs[0].buf[0]; + + switch (count) { + case 1: + if (msgs[0].flags & I2C_M_RD) { + if (msgs[0].len > 1) { + g_printerr("Incorrect message length for read operation: %d\n", + msgs[0].len); + return -1; + } + smbus_data.read_write = I2C_SMBUS_READ; + } else { + smbus_data.read_write = I2C_SMBUS_WRITE; + } + + if (!msgs[0].len) { + smbus_data.size = I2C_SMBUS_QUICK; + smbus_data.data = NULL; + } else if (msgs[0].len == 1) { + smbus_data.size = I2C_SMBUS_BYTE; + if (smbus_data.read_write == I2C_SMBUS_WRITE) { + smbus_data.data = NULL; + } + } else if (msgs[0].len == 2) { + smbus_data.size = I2C_SMBUS_BYTE_DATA; + data.byte = msgs[0].buf[1]; + } else if (msgs[0].len == 3) { + smbus_data.size = I2C_SMBUS_WORD_DATA; + data.word = msgs[0].buf[1] | (msgs[0].buf[2] << 8); + } else { + g_printerr("Message length not supported for write operation: %d\n", + msgs[0].len); + return -1; + } + + break; + case 2: + if ((msgs[0].flags & I2C_M_RD) || !(msgs[1].flags & I2C_M_RD) || + (msgs[0].len != 1) || (msgs[1].len > 2)) { + g_printerr("Expecting a valid read smbus transfer: %ld: %d: %d\n", + count, msgs[0].len, msgs[1].len); + return -1; + } + + smbus_data.read_write = I2C_SMBUS_READ; + smbus_data.size = msgs[1].len == 1 ? + I2C_SMBUS_BYTE_DATA : I2C_SMBUS_WORD_DATA; + break; + default: + g_printerr("Invalid number of messages for smbus xfer: %ld\n", count); + return -1; + } + + if (verbose) { + g_print("SMBUS command: %x: %x: %x\n", smbus_data.read_write, + smbus_data.command, smbus_data.size); + } + + ret = ioctl(adapter->fd, I2C_SMBUS, &smbus_data); + if (ret < 0) { + return ret; + } + + if (smbus_data.read_write == I2C_SMBUS_WRITE) { + return 0; + } + + switch (smbus_data.size) { + case I2C_SMBUS_BYTE: + msgs[0].buf[0] = data.byte; + break; + case I2C_SMBUS_BYTE_DATA: + msgs[1].buf[0] = data.byte; + break; + case I2C_SMBUS_WORD_DATA: + msgs[1].buf[0] = data.word & 0xff; + msgs[1].buf[1] = data.word >> 8; + break; + } + + return 0; +} + +static uint8_t vi2c_xfer(VuDev *dev, struct i2c_msg *msgs, size_t count) +{ + VuI2c *i2c = container_of(dev, VuI2c, dev.parent); + VI2cAdapter *adapter; + int8_t ret; + + /* Assume that adapter should be same for all messages sent together */ + adapter = vi2c_find_adapter(i2c, msgs[0].addr); + if (!adapter) { + g_printerr("Failed to find adapter for address: %x\n", msgs[0].addr); + return VIRTIO_I2C_MSG_ERR; + } + + /* Set client's address */ + if (!vi2c_set_client_addr(adapter, msgs[0].addr)) { + return VIRTIO_I2C_MSG_ERR; + } + + if (adapter->smbus) { + ret = smbus_xfer(adapter, msgs, count); + } else { + ret = i2c_xfer(adapter, msgs, count); + } + + if (ret < 0) { + g_printerr("Failed to transfer data to address %x : %d\n", + msgs[0].addr, errno); + return VIRTIO_I2C_MSG_ERR; + } + + if (verbose) { + vi2c_dump_msg(msgs, count); + } + + return VIRTIO_I2C_MSG_OK; +} + +/* Virtio helpers */ +static uint64_t vi2c_get_features(VuDev *dev) +{ + if (verbose) { + g_info("%s: replying", __func__); + } + return 0; +} + +static void vi2c_set_features(VuDev *dev, uint64_t features) +{ + if (verbose && features) { + g_autoptr(GString) s = g_string_new("Requested un-handled feature"); + g_string_append_printf(s, " 0x%" PRIx64 "", features); + g_info("%s: %s", __func__, s->str); + } +} + +static void vi2c_handle_ctrl(VuDev *dev, int qidx) +{ + VuVirtq *vq = vu_get_queue(dev, qidx); + struct virtio_i2c_out_hdr *out_hdr; + size_t i, count = 0, len, in_hdr_len; + struct i2c_msg *msgs; + VuVirtqElement *elem; + uint8_t status; + struct { + struct virtio_i2c_in_hdr *in_hdr; + VuVirtqElement *elem; + size_t size; + } *info; + + /* Count number of messages */ + do { + elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); + } while (elem && ++count); + + if (!count) { + if (verbose) { + g_printerr("Virtqueue can't have 0 elements\n"); + } + return; + } + + /* Rewind everything, now that we have counted the number of messages */ + vu_queue_rewind(dev, vq, count); + + if (verbose) { + g_print("Received %ld messages in virtqueue\n", count); + } + + /* Allocate memory for msgs and info */ + msgs = g_malloc0_n(count, sizeof(*msgs) + sizeof(*info)); + if (!msgs) { + g_printerr("failed to allocate space for messages\n"); + return; + } + info = (void *)(msgs + count); + + for (i = 0; i < count; i++) { + elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); + if (!elem) { + g_printerr("Failed to pop element: %ld : %ld\n", i, count); + goto out; + } + info[i].elem = elem; + info[i].size = sizeof(*info[i].in_hdr); + + g_debug("%s: got queue (in %d, out %d)", __func__, elem->in_num, + elem->out_num); + + /* Validate size of "out" header */ + if (elem->out_sg[0].iov_len != sizeof(*out_hdr)) { + g_warning("%s: Invalid out hdr %zu : %zu\n", __func__, + elem->out_sg[0].iov_len, sizeof(*out_hdr)); + goto out; + } + + out_hdr = elem->out_sg[0].iov_base; + + /* Bit 0 is reserved in virtio spec */ + msgs[i].addr = le16toh(out_hdr->addr) >> 1; + + /* Read Operation */ + if (elem->out_num == 1 && elem->in_num == 2) { + len = elem->in_sg[0].iov_len; + if (!len) { + g_warning("%s: Read buffer length can't be zero\n", __func__); + goto out; + } + + msgs[i].buf = elem->in_sg[0].iov_base; + msgs[i].flags = I2C_M_RD; + msgs[i].len = len; + + info[i].in_hdr = elem->in_sg[1].iov_base; + info[i].size += len; + in_hdr_len = elem->in_sg[1].iov_len; + } else if (elem->out_num == 2 && elem->in_num == 1) { + /* Write Operation */ + len = elem->out_sg[1].iov_len; + if (!len) { + g_warning("%s: Write buffer length can't be zero\n", __func__); + goto out; + } + + msgs[i].buf = elem->out_sg[1].iov_base; + msgs[i].flags = 0; + msgs[i].len = len; + + info[i].in_hdr = elem->in_sg[0].iov_base; + in_hdr_len = elem->in_sg[0].iov_len; + } else { + g_warning("%s: Transfer type not supported (in %d, out %d)\n", + __func__, elem->in_num, elem->out_num); + goto out; + } + + /* Validate size of "in" header */ + if (in_hdr_len != sizeof(*(info[i].in_hdr))) { + g_warning("%s: Invalid in hdr %zu : %zu\n", __func__, in_hdr_len, + sizeof(*(info[i].in_hdr))); + goto out; + } + } + + status = vi2c_xfer(dev, msgs, count); + + for (i = 0; i < count; i++) { + info[i].in_hdr->status = status; + vu_queue_push(dev, vq, info[i].elem, info[i].size); + } + + vu_queue_notify(dev, vq); + +out: + g_free(msgs); +} + +static void +vi2c_queue_set_started(VuDev *dev, int qidx, bool started) +{ + VuVirtq *vq = vu_get_queue(dev, qidx); + + g_debug("queue started %d:%d\n", qidx, started); + + if (!qidx) { + vu_set_queue_handler(dev, vq, started ? vi2c_handle_ctrl : NULL); + } +} + +/* + * vi2c_process_msg: process messages of vhost-user interface + * + * Any that are not handled here are processed by the libvhost library + * itself. + */ +static int vi2c_process_msg(VuDev *dev, VhostUserMsg *msg, int *do_reply) +{ + VuI2c *i2c = container_of(dev, VuI2c, dev.parent); + + if (msg->request == VHOST_USER_NONE) { + g_main_loop_quit(i2c->loop); + return 1; + } + + return 0; +} + +static const VuDevIface vuiface = { + .set_features = vi2c_set_features, + .get_features = vi2c_get_features, + .queue_set_started = vi2c_queue_set_started, + .process_msg = vi2c_process_msg, +}; + +static gboolean hangup(gpointer user_data) +{ + GMainLoop *loop = (GMainLoop *) user_data; + g_info("%s: caught hangup/quit signal, quitting main loop", __func__); + g_main_loop_quit(loop); + return true; +} + +static void vi2c_panic(VuDev *dev, const char *msg) +{ + g_critical("%s\n", msg); + exit(EXIT_FAILURE); +} + +/* Print vhost-user.json backend program capabilities */ +static void print_capabilities(void) +{ + printf("{\n"); + printf(" "type": "i2c"\n"); + printf(" "device-list"\n"); + printf("}\n"); +} + +static void vi2c_destroy(VuI2c *i2c) +{ + vi2c_remove_adapters(i2c); + vug_deinit(&i2c->dev); + if (socket_path) { + unlink(socket_path); + } +} + +int main(int argc, char *argv[]) +{ + GError *error = NULL; + GOptionContext *context; + g_autoptr(GSocket) socket = NULL; + VuI2c i2c = {0}; + + context = g_option_context_new("vhost-user emulation of I2C device"); + g_option_context_add_main_entries(context, options, "vhost-user-i2c"); + if (!g_option_context_parse(context, &argc, &argv, &error)) { + g_printerr("option parsing failed: %s\n", error->message); + exit(1); + } + + if (print_cap) { + print_capabilities(); + exit(0); + } + + if (!socket_path && socket_fd < 0) { + g_printerr("Please specify either --fd or --socket-path\n"); + exit(EXIT_FAILURE); + } + + if (verbose) { + g_log_set_handler(NULL, G_LOG_LEVEL_MASK, g_log_default_handler, NULL); + g_setenv("G_MESSAGES_DEBUG", "all", true); + } else { + g_log_set_handler(NULL, G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL | + G_LOG_LEVEL_ERROR, g_log_default_handler, NULL); + } + + /* + * Now create a vhost-user socket that we will receive messages + * on. Once we have our handler set up we can enter the glib main + * loop. + */ + if (socket_path) { + g_autoptr(GSocketAddress) addr = g_unix_socket_address_new(socket_path); + g_autoptr(GSocket) bind_socket = g_socket_new(G_SOCKET_FAMILY_UNIX, + G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, &error); + + if (!g_socket_bind(bind_socket, addr, false, &error)) { + g_printerr("Failed to bind to socket at %s (%s).\n", + socket_path, error->message); + exit(EXIT_FAILURE); + } + if (!g_socket_listen(bind_socket, &error)) { + g_printerr("Failed to listen on socket %s (%s).\n", + socket_path, error->message); + } + g_message("awaiting connection to %s", socket_path); + socket = g_socket_accept(bind_socket, NULL, &error); + if (!socket) { + g_printerr("Failed to accept on socket %s (%s).\n", + socket_path, error->message); + } + } else { + socket = g_socket_new_from_fd(socket_fd, &error); + if (!socket) { + g_printerr("Failed to connect to FD %d (%s).\n", + socket_fd, error->message); + exit(EXIT_FAILURE); + } + } + + if (vi2c_parse(&i2c)) { + exit(EXIT_FAILURE); + } + + /* + * Create the main loop first so all the various sources can be + * added. As well as catching signals we need to ensure vug_init + * can add it's GSource watches. + */ + + i2c.loop = g_main_loop_new(NULL, FALSE); + /* catch exit signals */ + g_unix_signal_add(SIGHUP, hangup, i2c.loop); + g_unix_signal_add(SIGINT, hangup, i2c.loop); + + if (!vug_init(&i2c.dev, VHOST_USER_I2C_MAX_QUEUES, g_socket_get_fd(socket), + vi2c_panic, &vuiface)) { + g_printerr("Failed to initialize libvhost-user-glib.\n"); + exit(EXIT_FAILURE); + } + + + g_message("entering main loop, awaiting messages"); + g_main_loop_run(i2c.loop); + g_message("finished main loop, cleaning up"); + + g_main_loop_unref(i2c.loop); + vi2c_destroy(&i2c); +} diff --git a/tools/vhost-user-i2c/meson.build b/tools/vhost-user-i2c/meson.build new file mode 100644 index 000000000000..f71e9fec7df6 --- /dev/null +++ b/tools/vhost-user-i2c/meson.build @@ -0,0 +1,10 @@ +executable('vhost-user-i2c', files( + 'main.c'), + dependencies: [qemuutil, glib, gio], + install: true, + install_dir: get_option('libexecdir')) + +configure_file(input: '50-qemu-i2c.json.in', + output: '50-qemu-i2c.json', + configuration: config_host, + install_dir: qemu_datadir / 'vhost-user')
Viresh Kumar viresh.kumar@linaro.org writes:
This adds the vhost-user backend driver to support virtio based I2C and SMBUS devices.
vhost-user-i2c --help
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
<snip>
+static VI2cAdapter *vi2c_create_adapter(int32_t bus, uint16_t client_addr[],
int32_t n_client)
+{
- VI2cAdapter *adapter;
- char path[20];
If you use:
g_autofree char *path = NULL;
If fact you could also use g_autofree with adapter to make you exit case a little easier.
- uint64_t funcs;
- int32_t fd, i;
- if (bus < 0) {
return NULL;
- }
- adapter = g_malloc0(sizeof(*adapter));
g_malloc will abort() on lack of memory so you can skip the check here.
- if (!adapter) {
g_printerr("failed to alloc adapter");
return NULL;
- }
- snprintf(path, sizeof(path), "/dev/i2c-%d", bus);
- path[sizeof(path) - 1] = '\0';
then this becomes:
path = g_strdup_printf("/dev/i2c-%d", bus);
- fd = open(path, O_RDWR);
- if (fd < 0) {
g_printerr("virtio_i2c: failed to open %s\n", path);
goto fail;
- }
- adapter->fd = fd;
- adapter->bus = bus;
- if (ioctl(fd, I2C_FUNCS, &funcs) < 0) {
g_printerr("virtio_i2c: failed to get functionality %s: %d\n", path,
errno);
goto close_fd;
- }
- if (funcs & I2C_FUNC_I2C) {
adapter->smbus = false;
- } else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) {
adapter->smbus = true;
- } else {
g_printerr("virtio_i2c: invalid functionality %lx\n", funcs);
goto close_fd;
- }
- for (i = 0; i < n_client; i++) {
if (client_addr[i]) {
if (!vi2c_set_client_addr(adapter, client_addr[i])) {
goto close_fd;
}
if (adapter->clients[client_addr[i]]) {
g_printerr("client addr 0x%x repeat, not allowed.\n",
client_addr[i]);
goto close_fd;
}
adapter->clients[client_addr[i]] = true;
if (verbose) {
g_print("Added client 0x%x to bus %u\n", client_addr[i], bus);
}
}
- }
- if (verbose) {
g_print("Added adapter: bus: %d, func %s\n", bus,
adapter->smbus ? "smbus" : "i2c");
- }
- return adapter;
This would then be:
return g_steal_pointer(adapter);
+close_fd:
- close(fd);
+fail:
- g_free(adapter);
- return NULL;
+}
+/*
- Virtio I2C device list format.
- <bus>:<client_addr>[:<client_addr>],
- [<bus>:<client_addr>[:<client_addr>]]
- bus (dec): adatper bus number.
e.g. 2 for /dev/i2c-2
- client_addr (hex): address for client device
e.g. 0x1C or 1C
- Example: --device-list="2:0x1c:0x20,3:0x10:0x2c"
- Note: client address can not repeat.
- */
+static int32_t vi2c_parse(VuI2c *i2c) +{
- uint16_t client_addr[MAX_I2C_VDEV];
- int32_t n_adapter = 0, n_client;
- int64_t addr, bus;
- const char *cp, *t;
- while (device_list) {
/* Read <bus>:<client_addr>[:<client_addr>] entries one by one */
cp = strsep(&device_list, ",");
The glib approach would be to use g_strsplit(device_list, ",", 0) which you can then iterate over with something like:
for (i = 0; cp[i] != NULL; i++) {
}
g_strfreev(cp);
if (!cp || *cp == '\0') {
break;
}
if (n_adapter == MAX_I2C_ADAPTER) {
g_printerr("too many adapter (%d), only support %d\n", n_adapter,
MAX_I2C_ADAPTER);
goto out;
}
if (qemu_strtol(cp, &t, 10, &bus) || bus < 0) {
g_printerr("Invalid bus number %s\n", cp);
goto out;
}
cp = t;
n_client = 0;
/* Parse clients <client_addr>[:<client_addr>] entries one by
one */
Then this would be:
**dev = g_strsplit(cp[i], ":", 2); if (dev && dev[0]) {
if (dev[1]) { } } g_freestrv(dev);
which would avoid you manually iterating over the string.
while (cp != NULL && *cp != '\0') {
if (*cp == ':') {
cp++;
}
if (n_client == MAX_I2C_VDEV) {
g_printerr("too many devices (%d), only support %d\n",
n_client, MAX_I2C_VDEV);
goto out;
}
if (qemu_strtol(cp, &t, 16, &addr) ||
addr < 0 || addr > MAX_I2C_VDEV) {
g_printerr("Invalid address %s : %lx\n", cp, addr);
goto out;
}
client_addr[n_client++] = addr;
cp = t;
if (verbose) {
g_print("i2c adapter %ld:0x%lx\n", bus, addr);
}
}
i2c->adapter[n_adapter] = vi2c_create_adapter(bus, client_addr,
n_client);
if (!i2c->adapter[n_adapter]) {
goto out;
}
n_adapter++;
- }
- if (!n_adapter) {
g_printerr("Failed to add any adapters\n");
return -1;
- }
- i2c->adapter_num = n_adapter;
- if (!vi2c_map_adapters(i2c)) {
return 0;
- }
+out:
- vi2c_remove_adapters(i2c);
- return -1;
+}
<snip>
+static void vi2c_handle_ctrl(VuDev *dev, int qidx) +{
- VuVirtq *vq = vu_get_queue(dev, qidx);
- struct virtio_i2c_out_hdr *out_hdr;
- size_t i, count = 0, len, in_hdr_len;
- struct i2c_msg *msgs;
- VuVirtqElement *elem;
- uint8_t status;
- struct {
struct virtio_i2c_in_hdr *in_hdr;
VuVirtqElement *elem;
size_t size;
- } *info;
- /* Count number of messages */
- do {
elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement));
- } while (elem && ++count);
Can you expand on this comment as to why we can't process each message as we come to it?
- if (!count) {
if (verbose) {
g_printerr("Virtqueue can't have 0 elements\n");
}
return;
- }
- /* Rewind everything, now that we have counted the number of messages */
- vu_queue_rewind(dev, vq, count);
- if (verbose) {
g_print("Received %ld messages in virtqueue\n", count);
- }
- /* Allocate memory for msgs and info */
- msgs = g_malloc0_n(count, sizeof(*msgs) + sizeof(*info));
- if (!msgs) {
g_printerr("failed to allocate space for messages\n");
return;
- }
g_malloc0_n will abort() or succeed. Could this be a g_new and be typed in some way?
- info = (void *)(msgs + count);
- for (i = 0; i < count; i++) {
elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement));
if (!elem) {
g_printerr("Failed to pop element: %ld : %ld\n", i, count);
goto out;
}
not withstanding the above comment this seems like an assert() because it should never fail?
info[i].elem = elem;
info[i].size = sizeof(*info[i].in_hdr);
g_debug("%s: got queue (in %d, out %d)", __func__, elem->in_num,
elem->out_num);
/* Validate size of "out" header */
if (elem->out_sg[0].iov_len != sizeof(*out_hdr)) {
g_warning("%s: Invalid out hdr %zu : %zu\n", __func__,
elem->out_sg[0].iov_len, sizeof(*out_hdr));
goto out;
}
out_hdr = elem->out_sg[0].iov_base;
/* Bit 0 is reserved in virtio spec */
msgs[i].addr = le16toh(out_hdr->addr) >> 1;
/* Read Operation */
if (elem->out_num == 1 && elem->in_num == 2) {
len = elem->in_sg[0].iov_len;
if (!len) {
g_warning("%s: Read buffer length can't be zero\n", __func__);
goto out;
}
msgs[i].buf = elem->in_sg[0].iov_base;
msgs[i].flags = I2C_M_RD;
msgs[i].len = len;
info[i].in_hdr = elem->in_sg[1].iov_base;
info[i].size += len;
in_hdr_len = elem->in_sg[1].iov_len;
} else if (elem->out_num == 2 && elem->in_num == 1) {
/* Write Operation */
len = elem->out_sg[1].iov_len;
if (!len) {
g_warning("%s: Write buffer length can't be zero\n", __func__);
goto out;
}
msgs[i].buf = elem->out_sg[1].iov_base;
msgs[i].flags = 0;
msgs[i].len = len;
info[i].in_hdr = elem->in_sg[0].iov_base;
in_hdr_len = elem->in_sg[0].iov_len;
} else {
g_warning("%s: Transfer type not supported (in %d, out %d)\n",
__func__, elem->in_num, elem->out_num);
goto out;
}
/* Validate size of "in" header */
if (in_hdr_len != sizeof(*(info[i].in_hdr))) {
g_warning("%s: Invalid in hdr %zu : %zu\n", __func__, in_hdr_len,
sizeof(*(info[i].in_hdr)));
goto out;
}
- }
- status = vi2c_xfer(dev, msgs, count);
- for (i = 0; i < count; i++) {
info[i].in_hdr->status = status;
vu_queue_push(dev, vq, info[i].elem, info[i].size);
- }
- vu_queue_notify(dev, vq);
+out:
- g_free(msgs);
+}
<snip>
diff --git a/tools/vhost-user-i2c/meson.build b/tools/vhost-user-i2c/meson.build new file mode 100644 index 000000000000..f71e9fec7df6 --- /dev/null +++ b/tools/vhost-user-i2c/meson.build @@ -0,0 +1,10 @@ +executable('vhost-user-i2c', files(
- 'main.c'),
- dependencies: [qemuutil, glib, gio],
- install: true,
- install_dir: get_option('libexecdir'))
+configure_file(input: '50-qemu-i2c.json.in',
output: '50-qemu-i2c.json',
configuration: config_host,
FYI this now needs to be:
configuration: { 'libexecdir' : get_option('prefix') / get_option('libexecdir') },
to properly pass libexecdir above.
install_dir: qemu_datadir / 'vhost-user')
On 01-04-21, 14:43, Alex Bennée wrote:
/* Parse clients <client_addr>[:<client_addr>] entries one by
one */
Then this would be:
**dev = g_strsplit(cp[i], ":", 2);
There can be any number of client devices present for a bus, not just 2.
On 2021/4/1 20:12, Viresh Kumar wrote:
+/* vhost-user-i2c definitions */
+#define MAX_I2C_VDEV (1 << 7) +#define MAX_I2C_ADAPTER 16
Generally speaking, 16 is big enough for most cases. But comparing with static configuration, I think it is better if we can check how many adapters in the host when doing initialization and use that number as "MAX_I2C_ADAPTER".
+static VI2cAdapter *vi2c_create_adapter(int32_t bus, uint16_t client_addr[],
int32_t n_client)
+{
- VI2cAdapter *adapter;
- char path[20];
- uint64_t funcs;
- int32_t fd, i;
- if (bus < 0) {
return NULL;
- }
- adapter = g_malloc0(sizeof(*adapter));
- if (!adapter) {
g_printerr("failed to alloc adapter");
return NULL;
- }
- snprintf(path, sizeof(path), "/dev/i2c-%d", bus);
- path[sizeof(path) - 1] = '\0';
- fd = open(path, O_RDWR);
- if (fd < 0) {
g_printerr("virtio_i2c: failed to open %s\n", path);
goto fail;
- }
- adapter->fd = fd;
- adapter->bus = bus;
- if (ioctl(fd, I2C_FUNCS, &funcs) < 0) {
g_printerr("virtio_i2c: failed to get functionality %s: %d\n", path,
errno);
goto close_fd;
- }
- if (funcs & I2C_FUNC_I2C) {
adapter->smbus = false;
- } else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) {
Only I2C_FUNC_SMBUS_WORD_DATA is checked here. But in addition to it, the smbus_xfer seems support I2C_FUNC_SMBUS_BYTE, I2C_FUNC_SMBUS_BYTE_DATA. So if an adapter only support the latter two, it will never go to smbus_xfer.
adapter->smbus = true;
- } else {
g_printerr("virtio_i2c: invalid functionality %lx\n", funcs);
goto close_fd;
- }
On 02-04-21, 10:55, Jie Deng wrote:
On 2021/4/1 20:12, Viresh Kumar wrote:
+/* vhost-user-i2c definitions */
+#define MAX_I2C_VDEV (1 << 7) +#define MAX_I2C_ADAPTER 16
Generally speaking, 16 is big enough for most cases. But comparing with static configuration, I think it is better if we can check how many adapters in the host when doing initialization and use that number as "MAX_I2C_ADAPTER".
Hmm, so this doesn't limit the adapter number in /dev/i2cX (i.e. we can still process /dev/i2c99), but rather how many adapters we can pass in --device-list="" parameter. And so I feel 16 is big enough and if someone really needs more, then they can update the macro here.
+static VI2cAdapter *vi2c_create_adapter(int32_t bus, uint16_t client_addr[],
int32_t n_client)
+{
- VI2cAdapter *adapter;
- char path[20];
- uint64_t funcs;
- int32_t fd, i;
- if (bus < 0) {
return NULL;
- }
- adapter = g_malloc0(sizeof(*adapter));
- if (!adapter) {
g_printerr("failed to alloc adapter");
return NULL;
- }
- snprintf(path, sizeof(path), "/dev/i2c-%d", bus);
- path[sizeof(path) - 1] = '\0';
- fd = open(path, O_RDWR);
- if (fd < 0) {
g_printerr("virtio_i2c: failed to open %s\n", path);
goto fail;
- }
- adapter->fd = fd;
- adapter->bus = bus;
- if (ioctl(fd, I2C_FUNCS, &funcs) < 0) {
g_printerr("virtio_i2c: failed to get functionality %s: %d\n", path,
errno);
goto close_fd;
- }
- if (funcs & I2C_FUNC_I2C) {
adapter->smbus = false;
- } else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) {
Only I2C_FUNC_SMBUS_WORD_DATA is checked here. But in addition to it, the smbus_xfer seems support I2C_FUNC_SMBUS_BYTE, I2C_FUNC_SMBUS_BYTE_DATA. So if an adapter only support the latter two, it will never go to smbus_xfer.
Yeah, I missed it. Thanks. I am updating it as:
- } else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) { + } else if (funcs & (I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA)) {
This adds the vhost-user backend driver to support virtio based I2C and SMBUS devices.
vhost-user-i2c --help
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- V2->2.1 - Check for more smbus functionalities. - Use better glibc helpers for string parsing. - Remove code to check return value of memory allocation. - Elaborate a comment. - Minor update to meson.build
tools/meson.build | 8 + tools/vhost-user-i2c/50-qemu-i2c.json.in | 5 + tools/vhost-user-i2c/main.c | 800 +++++++++++++++++++++++ tools/vhost-user-i2c/meson.build | 10 + 4 files changed, 823 insertions(+) create mode 100644 tools/vhost-user-i2c/50-qemu-i2c.json.in create mode 100644 tools/vhost-user-i2c/main.c create mode 100644 tools/vhost-user-i2c/meson.build
diff --git a/tools/meson.build b/tools/meson.build index 3e5a0abfa29f..8271e110978b 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -24,3 +24,11 @@ endif if have_virtiofsd subdir('virtiofsd') endif + +have_virtioi2c= (have_system and + have_tools and + 'CONFIG_LINUX' in config_host) + +if have_virtioi2c + subdir('vhost-user-i2c') +endif diff --git a/tools/vhost-user-i2c/50-qemu-i2c.json.in b/tools/vhost-user-i2c/50-qemu-i2c.json.in new file mode 100644 index 000000000000..dafd1337fa9c --- /dev/null +++ b/tools/vhost-user-i2c/50-qemu-i2c.json.in @@ -0,0 +1,5 @@ +{ + "description": "QEMU vhost-user-i2c", + "type": "bridge", + "binary": "@libexecdir@/vhost-user-i2c" +} diff --git a/tools/vhost-user-i2c/main.c b/tools/vhost-user-i2c/main.c new file mode 100644 index 000000000000..b9c7f729e882 --- /dev/null +++ b/tools/vhost-user-i2c/main.c @@ -0,0 +1,800 @@ +/* + * VIRTIO I2C Emulation via vhost-user + * + * Copyright (c) 2021 Viresh Kumar viresh.kumar@linaro.org + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define G_LOG_DOMAIN "vhost-user-i2c" +#define G_LOG_USE_STRUCTURED 1 + +#include <assert.h> +#include <endian.h> +#include <fcntl.h> +#include <gio/gio.h> +#include <gio/gunixsocketaddress.h> +#include <glib.h> +#include <glib-unix.h> +#include <glib/gstdio.h> +#include <inttypes.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "qemu/compiler.h" +#include "qemu/cutils.h" +#include "standard-headers/linux/virtio_i2c.h" +#include "subprojects/libvhost-user/libvhost-user-glib.h" +#include "subprojects/libvhost-user/libvhost-user.h" + +#define VHOST_USER_I2C_MAX_QUEUES 1 + +/* vhost-user-i2c definitions */ + +#define MAX_I2C_VDEV (1 << 7) +#define MAX_I2C_ADAPTER 16 + +typedef struct { + int32_t fd; + int32_t bus; + bool smbus; + bool clients[MAX_I2C_VDEV]; +} VI2cAdapter; + +typedef struct { + VugDev dev; + GMainLoop *loop; + VI2cAdapter *adapter[MAX_I2C_ADAPTER]; + uint16_t adapter_map[MAX_I2C_VDEV]; + uint32_t adapter_num; +} VuI2c; + +static gboolean print_cap, verbose; +static gchar *socket_path, *device_list; +static gint socket_fd = -1; + +static GOptionEntry options[] = { + { "socket-path", 's', 0, G_OPTION_ARG_FILENAME, &socket_path, + "Location of vhost-user Unix domain socket, incompatible with --fd", + "PATH" }, + { "fd", 'f', 0, G_OPTION_ARG_INT, &socket_fd, + "Specify file-descriptor of the backend, don't use with --socket-path", + "FD" }, + { "device-list", 'l', 0, G_OPTION_ARG_STRING, &device_list, + "List of i2c-dev bus and attached devices", "I2C Devices" }, + { "print-capabilities", 'c', 0, G_OPTION_ARG_NONE, &print_cap, + "Output to stdout the backend capabilities in JSON format and exit", + NULL}, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "Be more verbose in output", NULL}, + { NULL } +}; + + +/* I2c helpers */ +static void fmt_bytes(GString *s, uint8_t *bytes, int len) +{ + int32_t i; + for (i = 0; i < len; i++) { + if (i && i % 16 == 0) { + g_string_append_c(s, '\n'); + } + g_string_append_printf(s, "%x ", bytes[i]); + } +} + +static void vi2c_dump_msg(struct i2c_msg *msgs, size_t count) +{ + int32_t i; + + for (i = 0; i < count; i++) { + g_autoptr(GString) s = g_string_new("\nI2c request: "); + + g_string_append_printf(s, "addr: %x\n", msgs[i].addr); + g_string_append_printf(s, "transfer len: %x\n", msgs[i].len); + + g_string_append_printf(s, "%s: ", msgs[i].flags & I2C_M_RD ? + "Data read" : "Data Written"); + fmt_bytes(s, (uint8_t *)msgs[i].buf, msgs[i].len); + g_string_append_printf(s, "\n"); + + g_debug("%s: %s", __func__, s->str); + } +} + +static int vi2c_map_adapters(VuI2c *i2c) +{ + VI2cAdapter *adapter; + int32_t i, client_addr; + + /* + * Flatten the map for client address and adapter to the array: + * + * adapter_map[MAX_I2C_VDEV]: + * + * Adapter | adapter2 | none | adapter1 | adapter3 | none| (val) + * |----------|-------|----------|----------|-----| + * Slave Address | addr 1 | none | addr 2 | addr 3 | none| (idx) + * |<-----------------------MAX_I2C_VDEV---------------->| + */ + for (i = 0; i < i2c->adapter_num; i++) { + adapter = i2c->adapter[i]; + + for (client_addr = 0; client_addr < MAX_I2C_VDEV; client_addr++) { + if (adapter->clients[client_addr]) { + if (i2c->adapter_map[client_addr]) { + g_printerr("client addr %x repeated, not supported!\n", + client_addr); + return -1; + } + + /* The array is initialized to 0, + 1 for index */ + i2c->adapter_map[client_addr] = i + 1; + if (verbose) { + g_print("client: 0x%x -> i2c adapter: %d\n", client_addr, + adapter->bus); + } + } + } + } + return 0; +} + +static VI2cAdapter *vi2c_find_adapter(VuI2c *i2c, uint16_t addr) +{ + if (addr < MAX_I2C_VDEV && (i2c->adapter_map[addr] != 0)) { + return i2c->adapter[i2c->adapter_map[addr] - 1]; + } + + return NULL; +} + +static bool vi2c_set_client_addr(VI2cAdapter *adapter, uint16_t addr) +{ + if (ioctl(adapter->fd, I2C_SLAVE, addr) < 0) { + if (errno == EBUSY) { + g_printerr("client device %x is busy!\n", addr); + } else { + g_printerr("client device %d does not exist!\n", addr); + } + return false; + } + return true; +} + +static void vi2c_remove_adapters(VuI2c *i2c) +{ + VI2cAdapter *adapter; + int32_t i; + + for (i = 0; i < MAX_I2C_ADAPTER; i++) { + adapter = i2c->adapter[i]; + if (!adapter) { + break; + } + + if (adapter->fd > 0) { + close(adapter->fd); + } + + g_free(adapter); + i2c->adapter[i] = NULL; + } +} + +static VI2cAdapter *vi2c_create_adapter(int32_t bus, uint16_t client_addr[], + int32_t n_client) +{ + g_autofree VI2cAdapter *adapter = NULL; + g_autofree char *path = NULL; + uint64_t funcs; + int32_t fd, i; + + if (bus < 0) { + return NULL; + } + + adapter = g_malloc0(sizeof(*adapter)); + path = g_strdup_printf("/dev/i2c-%d", bus); + + fd = open(path, O_RDWR); + if (fd < 0) { + g_printerr("virtio_i2c: failed to open %s\n", path); + return NULL; + } + + adapter->fd = fd; + adapter->bus = bus; + + if (ioctl(fd, I2C_FUNCS, &funcs) < 0) { + g_printerr("virtio_i2c: failed to get functionality %s: %d\n", path, + errno); + goto close_fd; + } + + if (funcs & I2C_FUNC_I2C) { + adapter->smbus = false; + } else if (funcs & (I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA)) { + adapter->smbus = true; + } else { + g_printerr("virtio_i2c: invalid functionality %lx\n", funcs); + goto close_fd; + } + + for (i = 0; i < n_client; i++) { + if (client_addr[i]) { + if (!vi2c_set_client_addr(adapter, client_addr[i])) { + goto close_fd; + } + + if (adapter->clients[client_addr[i]]) { + g_printerr("client addr 0x%x repeat, not allowed.\n", + client_addr[i]); + goto close_fd; + } + + adapter->clients[client_addr[i]] = true; + if (verbose) { + g_print("Added client 0x%x to bus %u\n", client_addr[i], bus); + } + } + } + + if (verbose) { + g_print("Added adapter: bus: %d, func %s\n", bus, + adapter->smbus ? "smbus" : "i2c"); + } + return g_steal_pointer(&adapter); + +close_fd: + close(fd); + return NULL; +} + +/* + * Virtio I2C device list format. + * + * <bus>:<client_addr>[:<client_addr>], + * [<bus>:<client_addr>[:<client_addr>]] + * + * bus (dec): adatper bus number. + * e.g. 2 for /dev/i2c-2 + * client_addr (hex): address for client device + * e.g. 0x1C or 1C + * + * Example: --device-list="2:0x1c:0x20,3:0x10:0x2c" + * + * Note: client address can not repeat. + */ +static int32_t vi2c_parse(VuI2c *i2c) +{ + uint16_t client_addr[MAX_I2C_VDEV]; + int32_t n_adapter = 0, n_client, i, j; + int64_t addr, bus; + gchar **cp, **dev; + + cp = g_strsplit(device_list, ",", 0); + + /* Read <bus>:<client_addr>[:<client_addr>] entries one by one */ + for (i = 0; cp[i] && *cp[i] != '\0'; i++) { + if (n_adapter == MAX_I2C_ADAPTER) { + g_printerr("too many adapter (%d), only support %d\n", n_adapter, + MAX_I2C_ADAPTER); + goto out; + } + + dev = g_strsplit(cp[i], ":", 0); + + if (qemu_strtol(dev[0], NULL, 10, &bus) || bus < 0) { + g_printerr("Invalid bus number %s\n", dev[0]); + goto out; + } + + /* Parse clients <client_addr>[:<client_addr>] entries one by one */ + for (n_client = 0, j = 1; dev[j] && *dev[j] != '\0'; j++) { + if (n_client == MAX_I2C_VDEV) { + g_printerr("too many devices (%d), only support %d\n", + n_client, MAX_I2C_VDEV); + g_strfreev(dev); + goto out; + } + + if (qemu_strtol(dev[j], NULL, 16, &addr) || + addr < 0 || addr > MAX_I2C_VDEV) { + g_printerr("Invalid address %s : %lx\n", dev[j], addr); + g_strfreev(dev); + goto out; + } + + client_addr[n_client++] = addr; + if (verbose) { + g_print("i2c adapter %ld:0x%lx\n", bus, addr); + } + } + g_strfreev(dev); + + i2c->adapter[n_adapter] = vi2c_create_adapter(bus, client_addr, + n_client); + if (!i2c->adapter[n_adapter]) { + goto out; + } + + n_adapter++; + } + g_strfreev(cp); + + if (!n_adapter) { + g_printerr("Failed to add any adapters\n"); + return -1; + } + + i2c->adapter_num = n_adapter; + + if (!vi2c_map_adapters(i2c)) { + return 0; + } + +out: + g_strfreev(cp); + vi2c_remove_adapters(i2c); + return -1; +} + +static int32_t i2c_xfer(VI2cAdapter *adapter, struct i2c_msg *msgs, + size_t count) +{ + struct i2c_rdwr_ioctl_data data; + + data.nmsgs = count; + data.msgs = msgs; + + return ioctl(adapter->fd, I2C_RDWR, &data); +} + +/* + * Based on Linux's drivers/i2c/i2c-core-smbus.c:i2c_smbus_xfer_emulated(). This + * function tries to reverse what Linux does, only support basic modes (up to + * word transfer). + */ +static int32_t smbus_xfer(VI2cAdapter *adapter, struct i2c_msg *msgs, + size_t count) +{ + union i2c_smbus_data data; + struct i2c_smbus_ioctl_data smbus_data = { .data = &data }; + int32_t ret; + + smbus_data.command = msgs[0].buf[0]; + + switch (count) { + case 1: + if (msgs[0].flags & I2C_M_RD) { + if (msgs[0].len > 1) { + g_printerr("Incorrect message length for read operation: %d\n", + msgs[0].len); + return -1; + } + smbus_data.read_write = I2C_SMBUS_READ; + } else { + smbus_data.read_write = I2C_SMBUS_WRITE; + } + + if (!msgs[0].len) { + smbus_data.size = I2C_SMBUS_QUICK; + smbus_data.data = NULL; + } else if (msgs[0].len == 1) { + smbus_data.size = I2C_SMBUS_BYTE; + if (smbus_data.read_write == I2C_SMBUS_WRITE) { + smbus_data.data = NULL; + } + } else if (msgs[0].len == 2) { + smbus_data.size = I2C_SMBUS_BYTE_DATA; + data.byte = msgs[0].buf[1]; + } else if (msgs[0].len == 3) { + smbus_data.size = I2C_SMBUS_WORD_DATA; + data.word = msgs[0].buf[1] | (msgs[0].buf[2] << 8); + } else { + g_printerr("Message length not supported for write operation: %d\n", + msgs[0].len); + return -1; + } + + break; + case 2: + if ((msgs[0].flags & I2C_M_RD) || !(msgs[1].flags & I2C_M_RD) || + (msgs[0].len != 1) || (msgs[1].len > 2)) { + g_printerr("Expecting a valid read smbus transfer: %ld: %d: %d\n", + count, msgs[0].len, msgs[1].len); + return -1; + } + + smbus_data.read_write = I2C_SMBUS_READ; + smbus_data.size = msgs[1].len == 1 ? + I2C_SMBUS_BYTE_DATA : I2C_SMBUS_WORD_DATA; + break; + default: + g_printerr("Invalid number of messages for smbus xfer: %ld\n", count); + return -1; + } + + if (verbose) { + g_print("SMBUS command: %x: %x: %x\n", smbus_data.read_write, + smbus_data.command, smbus_data.size); + } + + ret = ioctl(adapter->fd, I2C_SMBUS, &smbus_data); + if (ret < 0) { + return ret; + } + + if (smbus_data.read_write == I2C_SMBUS_WRITE) { + return 0; + } + + switch (smbus_data.size) { + case I2C_SMBUS_BYTE: + msgs[0].buf[0] = data.byte; + break; + case I2C_SMBUS_BYTE_DATA: + msgs[1].buf[0] = data.byte; + break; + case I2C_SMBUS_WORD_DATA: + msgs[1].buf[0] = data.word & 0xff; + msgs[1].buf[1] = data.word >> 8; + break; + } + + return 0; +} + +static uint8_t vi2c_xfer(VuDev *dev, struct i2c_msg *msgs, size_t count) +{ + VuI2c *i2c = container_of(dev, VuI2c, dev.parent); + VI2cAdapter *adapter; + int8_t ret; + + /* Assume that adapter should be same for all messages sent together */ + adapter = vi2c_find_adapter(i2c, msgs[0].addr); + if (!adapter) { + g_printerr("Failed to find adapter for address: %x\n", msgs[0].addr); + return VIRTIO_I2C_MSG_ERR; + } + + /* Set client's address */ + if (!vi2c_set_client_addr(adapter, msgs[0].addr)) { + return VIRTIO_I2C_MSG_ERR; + } + + if (adapter->smbus) { + ret = smbus_xfer(adapter, msgs, count); + } else { + ret = i2c_xfer(adapter, msgs, count); + } + + if (ret < 0) { + g_printerr("Failed to transfer data to address %x : %d\n", + msgs[0].addr, errno); + return VIRTIO_I2C_MSG_ERR; + } + + if (verbose) { + vi2c_dump_msg(msgs, count); + } + + return VIRTIO_I2C_MSG_OK; +} + +/* Virtio helpers */ +static uint64_t vi2c_get_features(VuDev *dev) +{ + if (verbose) { + g_info("%s: replying", __func__); + } + return 0; +} + +static void vi2c_set_features(VuDev *dev, uint64_t features) +{ + if (verbose && features) { + g_autoptr(GString) s = g_string_new("Requested un-handled feature"); + g_string_append_printf(s, " 0x%" PRIx64 "", features); + g_info("%s: %s", __func__, s->str); + } +} + +static void vi2c_handle_ctrl(VuDev *dev, int qidx) +{ + VuVirtq *vq = vu_get_queue(dev, qidx); + struct virtio_i2c_out_hdr *out_hdr; + size_t i, count = 0, len, in_hdr_len; + struct i2c_msg *msgs; + VuVirtqElement *elem; + uint8_t status; + struct { + struct virtio_i2c_in_hdr *in_hdr; + VuVirtqElement *elem; + size_t size; + } *info; + + /* + * Count number of messages. + * + * We don't necessarily need to do this for transfers with I2C masters, but + * for SMBUS transfers, where is single read operation can come in two + * different virtio messages. + * + * Otherwise too for the I2C transfers, it optimizes the transfers anyway as + * we don't need to call the ioctl again and again. + */ + do { + elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); + } while (elem && ++count); + + if (!count) { + if (verbose) { + g_printerr("Virtqueue can't have 0 elements\n"); + } + return; + } + + /* Rewind everything, now that we have counted the number of messages */ + vu_queue_rewind(dev, vq, count); + + if (verbose) { + g_print("Received %ld messages in virtqueue\n", count); + } + + /* Allocate memory for msgs and info */ + msgs = g_malloc0_n(count, sizeof(*msgs) + sizeof(*info)); + info = (void *)(msgs + count); + + for (i = 0; i < count; i++) { + elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); + assert(elem); + + info[i].elem = elem; + info[i].size = sizeof(*info[i].in_hdr); + + g_debug("%s: got queue (in %d, out %d)", __func__, elem->in_num, + elem->out_num); + + /* Validate size of "out" header */ + if (elem->out_sg[0].iov_len != sizeof(*out_hdr)) { + g_warning("%s: Invalid out hdr %zu : %zu\n", __func__, + elem->out_sg[0].iov_len, sizeof(*out_hdr)); + goto out; + } + + out_hdr = elem->out_sg[0].iov_base; + + /* Bit 0 is reserved in virtio spec */ + msgs[i].addr = le16toh(out_hdr->addr) >> 1; + + /* Read Operation */ + if (elem->out_num == 1 && elem->in_num == 2) { + len = elem->in_sg[0].iov_len; + if (!len) { + g_warning("%s: Read buffer length can't be zero\n", __func__); + goto out; + } + + msgs[i].buf = elem->in_sg[0].iov_base; + msgs[i].flags = I2C_M_RD; + msgs[i].len = len; + + info[i].in_hdr = elem->in_sg[1].iov_base; + info[i].size += len; + in_hdr_len = elem->in_sg[1].iov_len; + } else if (elem->out_num == 2 && elem->in_num == 1) { + /* Write Operation */ + len = elem->out_sg[1].iov_len; + if (!len) { + g_warning("%s: Write buffer length can't be zero\n", __func__); + goto out; + } + + msgs[i].buf = elem->out_sg[1].iov_base; + msgs[i].flags = 0; + msgs[i].len = len; + + info[i].in_hdr = elem->in_sg[0].iov_base; + in_hdr_len = elem->in_sg[0].iov_len; + } else { + g_warning("%s: Transfer type not supported (in %d, out %d)\n", + __func__, elem->in_num, elem->out_num); + goto out; + } + + /* Validate size of "in" header */ + if (in_hdr_len != sizeof(*(info[i].in_hdr))) { + g_warning("%s: Invalid in hdr %zu : %zu\n", __func__, in_hdr_len, + sizeof(*(info[i].in_hdr))); + goto out; + } + } + + status = vi2c_xfer(dev, msgs, count); + + for (i = 0; i < count; i++) { + info[i].in_hdr->status = status; + vu_queue_push(dev, vq, info[i].elem, info[i].size); + } + + vu_queue_notify(dev, vq); + +out: + g_free(msgs); +} + +static void +vi2c_queue_set_started(VuDev *dev, int qidx, bool started) +{ + VuVirtq *vq = vu_get_queue(dev, qidx); + + g_debug("queue started %d:%d\n", qidx, started); + + if (!qidx) { + vu_set_queue_handler(dev, vq, started ? vi2c_handle_ctrl : NULL); + } +} + +/* + * vi2c_process_msg: process messages of vhost-user interface + * + * Any that are not handled here are processed by the libvhost library + * itself. + */ +static int vi2c_process_msg(VuDev *dev, VhostUserMsg *msg, int *do_reply) +{ + VuI2c *i2c = container_of(dev, VuI2c, dev.parent); + + if (msg->request == VHOST_USER_NONE) { + g_main_loop_quit(i2c->loop); + return 1; + } + + return 0; +} + +static const VuDevIface vuiface = { + .set_features = vi2c_set_features, + .get_features = vi2c_get_features, + .queue_set_started = vi2c_queue_set_started, + .process_msg = vi2c_process_msg, +}; + +static gboolean hangup(gpointer user_data) +{ + GMainLoop *loop = (GMainLoop *) user_data; + g_info("%s: caught hangup/quit signal, quitting main loop", __func__); + g_main_loop_quit(loop); + return true; +} + +static void vi2c_panic(VuDev *dev, const char *msg) +{ + g_critical("%s\n", msg); + exit(EXIT_FAILURE); +} + +/* Print vhost-user.json backend program capabilities */ +static void print_capabilities(void) +{ + printf("{\n"); + printf(" "type": "i2c"\n"); + printf(" "device-list"\n"); + printf("}\n"); +} + +static void vi2c_destroy(VuI2c *i2c) +{ + vi2c_remove_adapters(i2c); + vug_deinit(&i2c->dev); + if (socket_path) { + unlink(socket_path); + } +} + +int main(int argc, char *argv[]) +{ + GError *error = NULL; + GOptionContext *context; + g_autoptr(GSocket) socket = NULL; + VuI2c i2c = {0}; + + context = g_option_context_new("vhost-user emulation of I2C device"); + g_option_context_add_main_entries(context, options, "vhost-user-i2c"); + if (!g_option_context_parse(context, &argc, &argv, &error)) { + g_printerr("option parsing failed: %s\n", error->message); + exit(1); + } + + if (print_cap) { + print_capabilities(); + exit(0); + } + + if (!socket_path && socket_fd < 0) { + g_printerr("Please specify either --fd or --socket-path\n"); + exit(EXIT_FAILURE); + } + + if (verbose) { + g_log_set_handler(NULL, G_LOG_LEVEL_MASK, g_log_default_handler, NULL); + g_setenv("G_MESSAGES_DEBUG", "all", true); + } else { + g_log_set_handler(NULL, G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL | + G_LOG_LEVEL_ERROR, g_log_default_handler, NULL); + } + + /* + * Now create a vhost-user socket that we will receive messages + * on. Once we have our handler set up we can enter the glib main + * loop. + */ + if (socket_path) { + g_autoptr(GSocketAddress) addr = g_unix_socket_address_new(socket_path); + g_autoptr(GSocket) bind_socket = g_socket_new(G_SOCKET_FAMILY_UNIX, + G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, &error); + + if (!g_socket_bind(bind_socket, addr, false, &error)) { + g_printerr("Failed to bind to socket at %s (%s).\n", + socket_path, error->message); + exit(EXIT_FAILURE); + } + if (!g_socket_listen(bind_socket, &error)) { + g_printerr("Failed to listen on socket %s (%s).\n", + socket_path, error->message); + } + g_message("awaiting connection to %s", socket_path); + socket = g_socket_accept(bind_socket, NULL, &error); + if (!socket) { + g_printerr("Failed to accept on socket %s (%s).\n", + socket_path, error->message); + } + } else { + socket = g_socket_new_from_fd(socket_fd, &error); + if (!socket) { + g_printerr("Failed to connect to FD %d (%s).\n", + socket_fd, error->message); + exit(EXIT_FAILURE); + } + } + + if (vi2c_parse(&i2c)) { + exit(EXIT_FAILURE); + } + + /* + * Create the main loop first so all the various sources can be + * added. As well as catching signals we need to ensure vug_init + * can add it's GSource watches. + */ + + i2c.loop = g_main_loop_new(NULL, FALSE); + /* catch exit signals */ + g_unix_signal_add(SIGHUP, hangup, i2c.loop); + g_unix_signal_add(SIGINT, hangup, i2c.loop); + + if (!vug_init(&i2c.dev, VHOST_USER_I2C_MAX_QUEUES, g_socket_get_fd(socket), + vi2c_panic, &vuiface)) { + g_printerr("Failed to initialize libvhost-user-glib.\n"); + exit(EXIT_FAILURE); + } + + + g_message("entering main loop, awaiting messages"); + g_main_loop_run(i2c.loop); + g_message("finished main loop, cleaning up"); + + g_main_loop_unref(i2c.loop); + vi2c_destroy(&i2c); +} diff --git a/tools/vhost-user-i2c/meson.build b/tools/vhost-user-i2c/meson.build new file mode 100644 index 000000000000..4b0156053f5d --- /dev/null +++ b/tools/vhost-user-i2c/meson.build @@ -0,0 +1,10 @@ +executable('vhost-user-i2c', files( + 'main.c'), + dependencies: [qemuutil, glib, gio], + install: true, + install_dir: get_option('libexecdir')) + +configure_file(input: '50-qemu-i2c.json.in', + output: '50-qemu-i2c.json', + configuration: { 'libexecdir' : get_option('prefix') / get_option('libexecdir') }, + install_dir: qemu_datadir / 'vhost-user')
Basic usage and example invocation.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- docs/tools/index.rst | 1 + docs/tools/vhost-user-i2c.rst | 75 +++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 docs/tools/vhost-user-i2c.rst
diff --git a/docs/tools/index.rst b/docs/tools/index.rst index 3a5829c17a54..af2519406ddf 100644 --- a/docs/tools/index.rst +++ b/docs/tools/index.rst @@ -17,3 +17,4 @@ QEMU Tools Guide qemu-trace-stap virtfs-proxy-helper virtiofsd + vhost-user-i2c diff --git a/docs/tools/vhost-user-i2c.rst b/docs/tools/vhost-user-i2c.rst new file mode 100644 index 000000000000..1f05c6473ad7 --- /dev/null +++ b/docs/tools/vhost-user-i2c.rst @@ -0,0 +1,75 @@ +QEMU vhost-user-i2c - I2C emulation backend +=========================================== + +Synopsis +-------- + +**vhost-user-i2c** [*OPTIONS*] + +Description +----------- + +This program is a vhost-user backend that emulates a VirtIO I2C bus. +This program takes the layout of the i2c bus and its devices on the host +OS and then talks to them via the /dev/i2c-X interface when a request +comes from the guest OS for an I2C or SMBUS device. + +This program is designed to work with QEMU's ``-device +vhost-user-i2c-pci`` but should work with any virtual machine monitor +(VMM) that supports vhost-user. See the Examples section below. + +Options +------- + +.. program:: vhost-user-i2c + +.. option:: -h, --help + + Print help. + +.. option:: -v, --verbose + + Increase verbosity of output + +.. option:: -s, --socket-path=PATH + + Listen on vhost-user UNIX domain socket at PATH. Incompatible with --fd. + +.. option:: -f, --fd=FDNUM + + Accept connections from vhost-user UNIX domain socket file descriptor FDNUM. + The file descriptor must already be listening for connections. + Incompatible with --socket-path. + +.. option:: -l, --device-list=I2C-DEVICES + + I2c device list at the host OS in the format: + <bus>:<client_addr>[:<client_addr>],[<bus>:<client_addr>[:<client_addr>]] + + Example: --device-list "2:1c:20,3:10:2c" + + Here, + bus (decimal): adatper bus number. e.g. 2 for /dev/i2c-2, 3 for /dev/i2c-3. + client_addr (hex): address for client device. e.g. 0x1C, 0x20, 0x10, 0x2C. + +Examples +-------- + +The daemon should be started first: + +:: + + host# vhost-user-i2c --socket-path=vi2c.sock --device-list 0:20 + +The QEMU invocation needs to create a chardev socket the device can +use to communicate as well as share the guests memory over a memfd. + +:: + + host# qemu-system \ + -chardev socket,path=vi2c.sock,id=vi2c \ + -device vhost-user-i2c-pci,chardev=vi2c,id=i2c \ + -m 4096 \ + -object memory-backend-file,id=mem,size=4G,mem-path=/dev/shm,share=on \ + -numa node,memdev=mem \ + ...
This patch adds entry for virtio-i2c related files in MAINTAINERS.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS index 9147e9a429a0..34e1b26c985a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2136,6 +2136,15 @@ F: docs/interop/vhost-user-gpu.rst F: contrib/vhost-user-gpu F: hw/display/vhost-user-*
+vhost-user-i2c +M: Viresh Kumar viresh.kumar@linaro.org +S: Supported +F: docs/tools/vhost-user-i2c.rst +F: hw/virtio/vhost-user-i2c.c +F: hw/virtio/vhost-user-i2c-pci.c +F: include/hw/virtio/vhost-user-i2c.h +F: tools/vhost-user-i2c/* + Cirrus VGA M: Gerd Hoffmann kraxel@redhat.com S: Odd Fixes
stratos-dev@op-lists.linaro.org