... | ... | ||
---|---|---|---|
26 | thread by Alex Bennée for his RPMB work: | 26 | thread by Alex Bennée for his RPMB work: |
27 | 27 | ||
28 | https://lore.kernel.org/qemu-devel/20200925125147.26943-1-alex.bennee@linaro.org/ | 28 | https://lore.kernel.org/qemu-devel/20200925125147.26943-1-alex.bennee@linaro.org/ |
29 | 29 | ||
30 | 30 | ||
31 | Testing: | 31 | I2C Testing: |
32 | ------- | 32 | ------------ |
33 | 33 | ||
34 | I didn't have access to a real hardware where I can play with a I2C | 34 | I didn't have access to a real hardware where I can play with a I2C |
35 | client device (like RTC, eeprom, etc) to verify the working of the | 35 | client device (like RTC, eeprom, etc) to verify the working of the |
36 | backend daemon, so I decided to test it on my x86 box itself with | 36 | backend daemon, so I decided to test it on my x86 box itself with |
37 | hierarchy of two ARM64 guests. | 37 | hierarchy of two ARM64 guests. |
... | ... | ||
89 | Reaches i2c-virtio.c (Linux bus driver in guest2) -> | 89 | Reaches i2c-virtio.c (Linux bus driver in guest2) -> |
90 | transfer over virtio -> | 90 | transfer over virtio -> |
91 | Reaches the qemu's vhost-i2c device emulation (running over guest1) -> | 91 | Reaches the qemu's vhost-i2c device emulation (running over guest1) -> |
92 | Reaches the backend daemon vhost-user-i2c started earlier (in guest1) -> | 92 | Reaches the backend daemon vhost-user-i2c started earlier (in guest1) -> |
93 | ioctl(/dev/i2c-0, I2C_RDWR, ..); (in guest1) -> | 93 | ioctl(/dev/i2c-0, I2C_RDWR, ..); (in guest1) -> |
94 | reaches qemu's hw/rtc/ds1338.c (running over host) | 94 | reaches qemu's hw/rtc/ds1338.c (running over host) |
95 | 95 | ||
96 | |||
97 | |||
98 | SMBUS Testing: | ||
99 | -------------- | ||
100 | |||
101 | I wasn't required to have such a tedious setup for testing out with | ||
102 | SMBUS devices. I was able to emulate a SMBUS device on my x86 machine | ||
103 | using i2c-stub driver. | ||
104 | |||
105 | $ modprobe i2c-stub chip_addr=0x20 | ||
106 | //Boot the arm64 guest now with i2c-virtio driver and then do: | ||
107 | $ echo al3320a 0x20 > /sys/class/i2c-adapter/i2c-0/new_device | ||
108 | $ cat /sys/bus/iio/devices/iio:device0/in_illuminance_raw | ||
109 | |||
110 | That's it. | ||
96 | 111 | ||
97 | I hope I was able to give a clear picture of my test setup here :) | 112 | I hope I was able to give a clear picture of my test setup here :) |
98 | 113 | ||
99 | Thanks. | 114 | Thanks. |
100 | 115 | ||
101 | Viresh Kumar (5): | 116 | V1->V2: |
117 | - Add a separate patch (not to be merged) to keep stuff temporarily | ||
118 | taken from Linux kernel. | ||
119 | |||
120 | - Support SMBUS devices/busses in the backend daemon. | ||
121 | |||
122 | - Fix lots of checkpatch warnings/errors. | ||
123 | |||
124 | - Some other bug fixes, suggestions, etc. | ||
125 | |||
126 | Viresh Kumar (6): | ||
127 | !Merge: Update virtio headers from kernel | ||
102 | hw/virtio: add boilerplate for vhost-user-i2c device | 128 | hw/virtio: add boilerplate for vhost-user-i2c device |
103 | hw/virtio: add vhost-user-i2c-pci boilerplate | 129 | hw/virtio: add vhost-user-i2c-pci boilerplate |
104 | tools/vhost-user-i2c: Add backend driver | 130 | tools/vhost-user-i2c: Add backend driver |
105 | docs: add a man page for vhost-user-i2c | 131 | docs: add a man page for vhost-user-i2c |
106 | MAINTAINERS: Add entry for virtio-i2c | 132 | MAINTAINERS: Add entry for virtio-i2c |
107 | 133 | ||
108 | MAINTAINERS | 9 + | 134 | MAINTAINERS | 9 + |
109 | docs/tools/index.rst | 1 + | 135 | docs/tools/index.rst | 1 + |
110 | docs/tools/vhost-user-i2c.rst | 75 +++ | 136 | docs/tools/vhost-user-i2c.rst | 75 ++ |
111 | hw/virtio/Kconfig | 5 + | 137 | hw/virtio/Kconfig | 5 + |
112 | hw/virtio/meson.build | 2 + | 138 | hw/virtio/meson.build | 2 + |
113 | hw/virtio/vhost-user-i2c-pci.c | 79 +++ | 139 | hw/virtio/vhost-user-i2c-pci.c | 69 ++ |
114 | hw/virtio/vhost-user-i2c.c | 286 +++++++++ | 140 | hw/virtio/vhost-user-i2c.c | 285 +++++++ |
115 | include/hw/virtio/vhost-user-i2c.h | 37 ++ | 141 | include/hw/virtio/vhost-user-i2c.h | 37 + |
142 | include/standard-headers/linux/virtio_i2c.h | 40 + | ||
116 | include/standard-headers/linux/virtio_ids.h | 1 + | 143 | include/standard-headers/linux/virtio_ids.h | 1 + |
117 | tools/meson.build | 8 + | 144 | tools/meson.build | 8 + |
118 | tools/vhost-user-i2c/50-qemu-i2c.json.in | 5 + | 145 | tools/vhost-user-i2c/50-qemu-i2c.json.in | 5 + |
119 | tools/vhost-user-i2c/main.c | 652 ++++++++++++++++++++ | 146 | tools/vhost-user-i2c/main.c | 809 ++++++++++++++++++++ |
120 | tools/vhost-user-i2c/meson.build | 10 + | 147 | tools/vhost-user-i2c/meson.build | 10 + |
121 | 13 files changed, 1170 insertions(+) | 148 | 14 files changed, 1356 insertions(+) |
122 | create mode 100644 docs/tools/vhost-user-i2c.rst | 149 | create mode 100644 docs/tools/vhost-user-i2c.rst |
123 | create mode 100644 hw/virtio/vhost-user-i2c-pci.c | 150 | create mode 100644 hw/virtio/vhost-user-i2c-pci.c |
124 | create mode 100644 hw/virtio/vhost-user-i2c.c | 151 | create mode 100644 hw/virtio/vhost-user-i2c.c |
125 | create mode 100644 include/hw/virtio/vhost-user-i2c.h | 152 | create mode 100644 include/hw/virtio/vhost-user-i2c.h |
153 | create mode 100644 include/standard-headers/linux/virtio_i2c.h | ||
126 | create mode 100644 tools/vhost-user-i2c/50-qemu-i2c.json.in | 154 | create mode 100644 tools/vhost-user-i2c/50-qemu-i2c.json.in |
127 | create mode 100644 tools/vhost-user-i2c/main.c | 155 | create mode 100644 tools/vhost-user-i2c/main.c |
128 | create mode 100644 tools/vhost-user-i2c/meson.build | 156 | create mode 100644 tools/vhost-user-i2c/meson.build |
129 | 157 | ||
130 | -- | 158 | -- |
131 | 2.25.0.rc1.19.g042ed3e048af | 159 | 2.25.0.rc1.19.g042ed3e048af |
132 | 160 | ||
133 | 161 | diff view generated by jsdifflib |
New patch | |||
---|---|---|---|
1 | DO NOT MERGE | ||
1 | 2 | ||
3 | Update Linux virtio headers with help of | ||
4 | |||
5 | ./scripts/update-linux-headers.sh ~/lsrc/linux.git | ||
6 | |||
7 | Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> | ||
8 | --- | ||
9 | include/standard-headers/linux/virtio_i2c.h | 40 +++++++++++++++++++++ | ||
10 | include/standard-headers/linux/virtio_ids.h | 1 + | ||
11 | 2 files changed, 41 insertions(+) | ||
12 | create mode 100644 include/standard-headers/linux/virtio_i2c.h | ||
13 | |||
14 | diff --git a/include/standard-headers/linux/virtio_i2c.h b/include/standard-headers/linux/virtio_i2c.h | ||
15 | new file mode 100644 | ||
16 | index XXXXXXX..XXXXXXX | ||
17 | --- /dev/null | ||
18 | +++ b/include/standard-headers/linux/virtio_i2c.h | ||
19 | @@ -XXX,XX +XXX,XX @@ | ||
20 | +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ | ||
21 | +/* | ||
22 | + * Definitions for virtio I2C Adpter | ||
23 | + * | ||
24 | + * Copyright (c) 2021 Intel Corporation. All rights reserved. | ||
25 | + */ | ||
26 | + | ||
27 | +#ifndef _LINUX_VIRTIO_I2C_H | ||
28 | +#define _LINUX_VIRTIO_I2C_H | ||
29 | + | ||
30 | +#include "standard-headers/linux/types.h" | ||
31 | + | ||
32 | +/* The bit 0 of the @virtio_i2c_out_hdr.@flags, used to group the requests */ | ||
33 | +#define VIRTIO_I2C_FLAGS_FAIL_NEXT 0x00000001 | ||
34 | + | ||
35 | +/** | ||
36 | + * struct virtio_i2c_out_hdr - the virtio I2C message OUT header | ||
37 | + * @addr: the controlled device address | ||
38 | + * @padding: used to pad to full dword | ||
39 | + * @flags: used for feature extensibility | ||
40 | + */ | ||
41 | +struct virtio_i2c_out_hdr { | ||
42 | + uint16_t addr; | ||
43 | + uint16_t padding; | ||
44 | + uint32_t flags; | ||
45 | +}; | ||
46 | + | ||
47 | +/** | ||
48 | + * struct virtio_i2c_in_hdr - the virtio I2C message IN header | ||
49 | + * @status: the processing result from the backend | ||
50 | + */ | ||
51 | +struct virtio_i2c_in_hdr { | ||
52 | + uint8_t status; | ||
53 | +}; | ||
54 | + | ||
55 | +/* The final status written by the device */ | ||
56 | +#define VIRTIO_I2C_MSG_OK 0 | ||
57 | +#define VIRTIO_I2C_MSG_ERR 1 | ||
58 | + | ||
59 | +#endif /* _LINUX_VIRTIO_I2C_H */ | ||
60 | diff --git a/include/standard-headers/linux/virtio_ids.h b/include/standard-headers/linux/virtio_ids.h | ||
61 | index XXXXXXX..XXXXXXX 100644 | ||
62 | --- a/include/standard-headers/linux/virtio_ids.h | ||
63 | +++ b/include/standard-headers/linux/virtio_ids.h | ||
64 | @@ -XXX,XX +XXX,XX @@ | ||
65 | #define VIRTIO_ID_FS 26 /* virtio filesystem */ | ||
66 | #define VIRTIO_ID_PMEM 27 /* virtio pmem */ | ||
67 | #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */ | ||
68 | +#define VIRTIO_ID_I2C_ADAPTER 34 /* virtio i2c adapter */ | ||
69 | |||
70 | #endif /* _LINUX_VIRTIO_IDS_H */ | ||
71 | -- | ||
72 | 2.25.0.rc1.19.g042ed3e048af | ||
73 | |||
74 | diff view generated by jsdifflib |
1 | This creates the QEMU side of the vhost-user-i2c device which connects | 1 | This creates the QEMU side of the vhost-user-i2c device which connects |
---|---|---|---|
2 | to the remote daemon. It is based of vhost-user-fs code. | 2 | to the remote daemon. It is based of vhost-user-fs code. |
3 | 3 | ||
4 | Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> | 4 | Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> |
5 | --- | 5 | --- |
6 | hw/virtio/Kconfig | 5 + | 6 | hw/virtio/Kconfig | 5 + |
7 | hw/virtio/meson.build | 1 + | 7 | hw/virtio/meson.build | 1 + |
8 | hw/virtio/vhost-user-i2c.c | 286 ++++++++++++++++++++ | 8 | hw/virtio/vhost-user-i2c.c | 285 +++++++++++++++++++++++++++++ |
9 | include/hw/virtio/vhost-user-i2c.h | 37 +++ | 9 | include/hw/virtio/vhost-user-i2c.h | 37 ++++ |
10 | include/standard-headers/linux/virtio_ids.h | 1 + | 10 | 4 files changed, 328 insertions(+) |
11 | 5 files changed, 330 insertions(+) | ||
12 | create mode 100644 hw/virtio/vhost-user-i2c.c | 11 | create mode 100644 hw/virtio/vhost-user-i2c.c |
13 | create mode 100644 include/hw/virtio/vhost-user-i2c.h | 12 | create mode 100644 include/hw/virtio/vhost-user-i2c.h |
14 | 13 | ||
15 | diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig | 14 | diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig |
16 | index XXXXXXX..XXXXXXX 100644 | 15 | index XXXXXXX..XXXXXXX 100644 |
... | ... | ||
181 | + | 180 | + |
182 | +static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserI2C *i2c) | 181 | +static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserI2C *i2c) |
183 | +{ | 182 | +{ |
184 | + vhost_user_cleanup(&i2c->vhost_user); | 183 | + vhost_user_cleanup(&i2c->vhost_user); |
185 | + virtio_delete_queue(i2c->req_vq); | 184 | + virtio_delete_queue(i2c->req_vq); |
186 | + g_free(i2c->vhost_dev.vqs); | ||
187 | + virtio_cleanup(vdev); | 185 | + virtio_cleanup(vdev); |
188 | + g_free(i2c->vhost_dev.vqs); | 186 | + g_free(i2c->vhost_dev.vqs); |
189 | + i2c->vhost_dev.vqs = NULL; | 187 | + i2c->vhost_dev.vqs = NULL; |
190 | +} | 188 | +} |
191 | + | 189 | + |
... | ... | ||
259 | + | 257 | + |
260 | + if (!vhost_user_init(&i2c->vhost_user, &i2c->conf.chardev, errp)) { | 258 | + if (!vhost_user_init(&i2c->vhost_user, &i2c->conf.chardev, errp)) { |
261 | + return; | 259 | + return; |
262 | + } | 260 | + } |
263 | + | 261 | + |
264 | + virtio_init(vdev, "vhost-user-i2c", VIRTIO_ID_I2C, 0); | 262 | + virtio_init(vdev, "vhost-user-i2c", VIRTIO_ID_I2C_ADAPTER, 0); |
265 | + | 263 | + |
266 | + i2c->req_vq = virtio_add_queue(vdev, 3, vu_i2c_handle_output); | 264 | + i2c->req_vq = virtio_add_queue(vdev, 4, vu_i2c_handle_output); |
267 | + i2c->vhost_dev.nvqs = 1; | 265 | + i2c->vhost_dev.nvqs = 1; |
268 | + i2c->vhost_dev.vqs = g_new0(struct vhost_virtqueue, i2c->vhost_dev.nvqs); | 266 | + i2c->vhost_dev.vqs = g_new0(struct vhost_virtqueue, i2c->vhost_dev.nvqs); |
269 | + ret = vhost_dev_init(&i2c->vhost_dev, &i2c->vhost_user, | 267 | + ret = vhost_dev_init(&i2c->vhost_dev, &i2c->vhost_user, |
270 | + VHOST_BACKEND_TYPE_USER, 0); | 268 | + VHOST_BACKEND_TYPE_USER, 0); |
271 | + if (ret < 0) { | 269 | + if (ret < 0) { |
... | ... | ||
370 | + | 368 | + |
371 | + /*< public >*/ | 369 | + /*< public >*/ |
372 | +}; | 370 | +}; |
373 | + | 371 | + |
374 | +#endif /* _QEMU_VHOST_USER_I2C_H */ | 372 | +#endif /* _QEMU_VHOST_USER_I2C_H */ |
375 | diff --git a/include/standard-headers/linux/virtio_ids.h b/include/standard-headers/linux/virtio_ids.h | ||
376 | index XXXXXXX..XXXXXXX 100644 | ||
377 | --- a/include/standard-headers/linux/virtio_ids.h | ||
378 | +++ b/include/standard-headers/linux/virtio_ids.h | ||
379 | @@ -XXX,XX +XXX,XX @@ | ||
380 | #define VIRTIO_ID_FS 26 /* virtio filesystem */ | ||
381 | #define VIRTIO_ID_PMEM 27 /* virtio pmem */ | ||
382 | #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */ | ||
383 | +#define VIRTIO_ID_I2C 34 /* virtio i2c */ | ||
384 | |||
385 | #endif /* _LINUX_VIRTIO_IDS_H */ | ||
386 | -- | 373 | -- |
387 | 2.25.0.rc1.19.g042ed3e048af | 374 | 2.25.0.rc1.19.g042ed3e048af |
388 | 375 | ||
389 | 376 | diff view generated by jsdifflib |
1 | This allows is to instantiate a vhost-user-i2c device as part of a PCI | 1 | This allows is to instantiate a vhost-user-i2c device as part of a PCI |
---|---|---|---|
2 | bus. It is mostly boilerplate which looks pretty similar to the | 2 | bus. It is mostly boilerplate which looks pretty similar to the |
3 | vhost-user-fs-pci device. | 3 | vhost-user-fs-pci device. |
4 | 4 | ||
5 | Reviewed-by: Alex Bennée <alex.bennee@linaro.org> | ||
5 | Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> | 6 | Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> |
6 | --- | 7 | --- |
7 | hw/virtio/meson.build | 1 + | 8 | hw/virtio/meson.build | 1 + |
8 | hw/virtio/vhost-user-i2c-pci.c | 79 ++++++++++++++++++++++++++++++++++ | 9 | hw/virtio/vhost-user-i2c-pci.c | 69 ++++++++++++++++++++++++++++++++++ |
9 | 2 files changed, 80 insertions(+) | 10 | 2 files changed, 70 insertions(+) |
10 | create mode 100644 hw/virtio/vhost-user-i2c-pci.c | 11 | create mode 100644 hw/virtio/vhost-user-i2c-pci.c |
11 | 12 | ||
12 | diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build | 13 | diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build |
13 | index XXXXXXX..XXXXXXX 100644 | 14 | index XXXXXXX..XXXXXXX 100644 |
14 | --- a/hw/virtio/meson.build | 15 | --- a/hw/virtio/meson.build |
... | ... | ||
50 | +#define TYPE_VHOST_USER_I2C_PCI "vhost-user-i2c-pci-base" | 51 | +#define TYPE_VHOST_USER_I2C_PCI "vhost-user-i2c-pci-base" |
51 | + | 52 | + |
52 | +DECLARE_INSTANCE_CHECKER(VHostUserI2CPCI, VHOST_USER_I2C_PCI, | 53 | +DECLARE_INSTANCE_CHECKER(VHostUserI2CPCI, VHOST_USER_I2C_PCI, |
53 | + TYPE_VHOST_USER_I2C_PCI) | 54 | + TYPE_VHOST_USER_I2C_PCI) |
54 | + | 55 | + |
55 | +static Property vhost_user_i2c_pci_properties[] = { | ||
56 | + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, | ||
57 | + DEV_NVECTORS_UNSPECIFIED), | ||
58 | + DEFINE_PROP_END_OF_LIST(), | ||
59 | +}; | ||
60 | + | ||
61 | +static void vhost_user_i2c_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) | 56 | +static void vhost_user_i2c_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) |
62 | +{ | 57 | +{ |
63 | + VHostUserI2CPCI *dev = VHOST_USER_I2C_PCI(vpci_dev); | 58 | + VHostUserI2CPCI *dev = VHOST_USER_I2C_PCI(vpci_dev); |
64 | + DeviceState *vdev = DEVICE(&dev->vdev); | 59 | + DeviceState *vdev = DEVICE(&dev->vdev); |
65 | + | 60 | + |
66 | + if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) { | 61 | + vpci_dev->nvectors = 1; |
67 | + vpci_dev->nvectors = 1; | ||
68 | + } | ||
69 | + | ||
70 | + qdev_realize(vdev, BUS(&vpci_dev->bus), errp); | 62 | + qdev_realize(vdev, BUS(&vpci_dev->bus), errp); |
71 | +} | 63 | +} |
72 | + | 64 | + |
73 | +static void vhost_user_i2c_pci_class_init(ObjectClass *klass, void *data) | 65 | +static void vhost_user_i2c_pci_class_init(ObjectClass *klass, void *data) |
74 | +{ | 66 | +{ |
75 | + DeviceClass *dc = DEVICE_CLASS(klass); | 67 | + DeviceClass *dc = DEVICE_CLASS(klass); |
76 | + VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); | 68 | + VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); |
77 | + PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); | 69 | + PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); |
78 | + k->realize = vhost_user_i2c_pci_realize; | 70 | + k->realize = vhost_user_i2c_pci_realize; |
79 | + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); | 71 | + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); |
80 | + device_class_set_props(dc, vhost_user_i2c_pci_properties); | ||
81 | + pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; | 72 | + pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; |
82 | + pcidev_k->device_id = 0; /* Set by virtio-pci based on virtio id */ | 73 | + pcidev_k->device_id = 0; /* Set by virtio-pci based on virtio id */ |
83 | + pcidev_k->revision = 0x00; | 74 | + pcidev_k->revision = 0x00; |
84 | + pcidev_k->class_id = PCI_CLASS_COMMUNICATION_OTHER; | 75 | + pcidev_k->class_id = PCI_CLASS_COMMUNICATION_OTHER; |
85 | +} | 76 | +} |
... | ... | diff view generated by jsdifflib |
1 | This adds the vhost-user backend driver to support virtio based I2C | 1 | This adds the vhost-user backend driver to support virtio based I2C and |
---|---|---|---|
2 | devices. | 2 | SMBUS devices. |
3 | 3 | ||
4 | vhost-user-i2c --help | 4 | vhost-user-i2c --help |
5 | 5 | ||
6 | Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> | 6 | Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> |
7 | --- | 7 | --- |
8 | hw/virtio/vhost-user-i2c.c | 2 +- | ||
9 | tools/meson.build | 8 + | 8 | tools/meson.build | 8 + |
10 | tools/vhost-user-i2c/50-qemu-i2c.json.in | 5 + | 9 | tools/vhost-user-i2c/50-qemu-i2c.json.in | 5 + |
11 | tools/vhost-user-i2c/main.c | 652 +++++++++++++++++++++++ | 10 | tools/vhost-user-i2c/main.c | 809 +++++++++++++++++++++++ |
12 | tools/vhost-user-i2c/meson.build | 10 + | 11 | tools/vhost-user-i2c/meson.build | 10 + |
13 | 5 files changed, 676 insertions(+), 1 deletion(-) | 12 | 4 files changed, 832 insertions(+) |
14 | create mode 100644 tools/vhost-user-i2c/50-qemu-i2c.json.in | 13 | create mode 100644 tools/vhost-user-i2c/50-qemu-i2c.json.in |
15 | create mode 100644 tools/vhost-user-i2c/main.c | 14 | create mode 100644 tools/vhost-user-i2c/main.c |
16 | create mode 100644 tools/vhost-user-i2c/meson.build | 15 | create mode 100644 tools/vhost-user-i2c/meson.build |
17 | 16 | ||
18 | diff --git a/hw/virtio/vhost-user-i2c.c b/hw/virtio/vhost-user-i2c.c | ||
19 | index XXXXXXX..XXXXXXX 100644 | ||
20 | --- a/hw/virtio/vhost-user-i2c.c | ||
21 | +++ b/hw/virtio/vhost-user-i2c.c | ||
22 | @@ -XXX,XX +XXX,XX @@ static void vu_i2c_device_realize(DeviceState *dev, Error **errp) | ||
23 | |||
24 | virtio_init(vdev, "vhost-user-i2c", VIRTIO_ID_I2C, 0); | ||
25 | |||
26 | - i2c->req_vq = virtio_add_queue(vdev, 3, vu_i2c_handle_output); | ||
27 | + i2c->req_vq = virtio_add_queue(vdev, 4, vu_i2c_handle_output); | ||
28 | i2c->vhost_dev.nvqs = 1; | ||
29 | i2c->vhost_dev.vqs = g_new0(struct vhost_virtqueue, i2c->vhost_dev.nvqs); | ||
30 | ret = vhost_dev_init(&i2c->vhost_dev, &i2c->vhost_user, | ||
31 | diff --git a/tools/meson.build b/tools/meson.build | 17 | diff --git a/tools/meson.build b/tools/meson.build |
32 | index XXXXXXX..XXXXXXX 100644 | 18 | index XXXXXXX..XXXXXXX 100644 |
33 | --- a/tools/meson.build | 19 | --- a/tools/meson.build |
34 | +++ b/tools/meson.build | 20 | +++ b/tools/meson.build |
35 | @@ -XXX,XX +XXX,XX @@ endif | 21 | @@ -XXX,XX +XXX,XX @@ endif |
... | ... | ||
70 | + */ | 56 | + */ |
71 | + | 57 | + |
72 | +#define G_LOG_DOMAIN "vhost-user-i2c" | 58 | +#define G_LOG_DOMAIN "vhost-user-i2c" |
73 | +#define G_LOG_USE_STRUCTURED 1 | 59 | +#define G_LOG_USE_STRUCTURED 1 |
74 | + | 60 | + |
75 | +#include <glib.h> | 61 | +#include <assert.h> |
62 | +#include <endian.h> | ||
63 | +#include <fcntl.h> | ||
76 | +#include <gio/gio.h> | 64 | +#include <gio/gio.h> |
77 | +#include <gio/gunixsocketaddress.h> | 65 | +#include <gio/gunixsocketaddress.h> |
66 | +#include <glib.h> | ||
78 | +#include <glib-unix.h> | 67 | +#include <glib-unix.h> |
79 | +#include <glib/gstdio.h> | 68 | +#include <glib/gstdio.h> |
80 | +#include <stdio.h> | ||
81 | +#include <stdbool.h> | ||
82 | +#include <string.h> | ||
83 | +#include <inttypes.h> | 69 | +#include <inttypes.h> |
84 | +#include <fcntl.h> | ||
85 | +#include <sys/ioctl.h> | ||
86 | +#include <sys/types.h> | ||
87 | +#include <sys/stat.h> | ||
88 | +#include <sys/mman.h> | ||
89 | +#include <unistd.h> | ||
90 | +#include <endian.h> | ||
91 | +#include <assert.h> | ||
92 | +#include <linux/i2c.h> | 70 | +#include <linux/i2c.h> |
93 | +#include <linux/i2c-dev.h> | 71 | +#include <linux/i2c-dev.h> |
94 | + | 72 | +#include <stdbool.h> |
73 | +#include <stdio.h> | ||
74 | +#include <string.h> | ||
75 | +#include <sys/ioctl.h> | ||
76 | +#include <sys/mman.h> | ||
77 | +#include <sys/stat.h> | ||
78 | +#include <sys/types.h> | ||
79 | +#include <unistd.h> | ||
80 | + | ||
81 | +#include "qemu/compiler.h" | ||
95 | +#include "qemu/cutils.h" | 82 | +#include "qemu/cutils.h" |
83 | +#include "standard-headers/linux/virtio_i2c.h" | ||
96 | +#include "subprojects/libvhost-user/libvhost-user-glib.h" | 84 | +#include "subprojects/libvhost-user/libvhost-user-glib.h" |
97 | +#include "subprojects/libvhost-user/libvhost-user.h" | 85 | +#include "subprojects/libvhost-user/libvhost-user.h" |
98 | + | 86 | + |
99 | +/* Definitions from virtio-i2c specifications */ | ||
100 | +#define VHOST_USER_I2C_MAX_QUEUES 1 | 87 | +#define VHOST_USER_I2C_MAX_QUEUES 1 |
101 | + | 88 | + |
102 | +/* Status */ | ||
103 | +#define VIRTIO_I2C_MSG_OK 0 | ||
104 | +#define VIRTIO_I2C_MSG_ERR 1 | ||
105 | + | ||
106 | +/* The bit 0 of the @virtio_i2c_out_hdr.@flags, used to group the requests */ | ||
107 | +#define VIRTIO_I2C_FLAGS_FAIL_NEXT 0x00000001 | ||
108 | + | ||
109 | +/** | ||
110 | + * struct virtio_i2c_out_hdr - the virtio I2C message OUT header | ||
111 | + * @addr: the controlled device's address | ||
112 | + * @padding: used to pad to full dword | ||
113 | + * @flags: used for feature extensibility | ||
114 | + */ | ||
115 | +struct virtio_i2c_out_hdr { | ||
116 | + uint16_t addr; | ||
117 | + uint16_t padding; | ||
118 | + uint32_t flags; | ||
119 | +} __attribute__((packed)); | ||
120 | + | ||
121 | +/** | ||
122 | + * struct virtio_i2c_in_hdr - the virtio I2C message IN header | ||
123 | + * @status: the processing result from the backend | ||
124 | + */ | ||
125 | +struct virtio_i2c_in_hdr { | ||
126 | + uint8_t status; | ||
127 | +} __attribute__((packed)); | ||
128 | + | ||
129 | +/* vhost-user-i2c definitions */ | 89 | +/* vhost-user-i2c definitions */ |
130 | + | ||
131 | +#ifndef container_of | ||
132 | +#define container_of(ptr, type, member) ({ \ | ||
133 | + const typeof(((type *) 0)->member) *__mptr = (ptr); \ | ||
134 | + (type *) ((char *) __mptr - offsetof(type, member));}) | ||
135 | +#endif | ||
136 | + | 90 | + |
137 | +#define MAX_I2C_VDEV (1 << 7) | 91 | +#define MAX_I2C_VDEV (1 << 7) |
138 | +#define MAX_I2C_ADAPTER 16 | 92 | +#define MAX_I2C_ADAPTER 16 |
139 | + | 93 | + |
140 | +typedef struct { | 94 | +typedef struct { |
141 | + int32_t fd; | 95 | + int32_t fd; |
142 | + int32_t bus; | 96 | + int32_t bus; |
97 | + bool smbus; | ||
143 | + bool clients[MAX_I2C_VDEV]; | 98 | + bool clients[MAX_I2C_VDEV]; |
144 | +} VI2cAdapter; | 99 | +} VI2cAdapter; |
145 | + | 100 | + |
146 | +typedef struct { | 101 | +typedef struct { |
147 | + VugDev dev; | 102 | + VugDev dev; |
... | ... | ||
154 | +static gboolean print_cap, verbose; | 109 | +static gboolean print_cap, verbose; |
155 | +static gchar *socket_path, *device_list; | 110 | +static gchar *socket_path, *device_list; |
156 | +static gint socket_fd = -1; | 111 | +static gint socket_fd = -1; |
157 | + | 112 | + |
158 | +static GOptionEntry options[] = { | 113 | +static GOptionEntry options[] = { |
159 | + { "socket-path", 's', 0, G_OPTION_ARG_FILENAME, &socket_path, "Location of vhost-user Unix domain socket, incompatible with --fd", "PATH" }, | 114 | + { "socket-path", 's', 0, G_OPTION_ARG_FILENAME, &socket_path, |
160 | + { "fd", 'f', 0, G_OPTION_ARG_INT, &socket_fd, "Specify the file-descriptor of the backend, incompatible with --socket-path", "FD" }, | 115 | + "Location of vhost-user Unix domain socket, incompatible with --fd", |
161 | + { "device-list", 'l', 0, G_OPTION_ARG_STRING, &device_list, "List of i2c-dev bus and attached devices", "I2C Devices" }, | 116 | + "PATH" }, |
162 | + { "print-capabilities", 'c', 0, G_OPTION_ARG_NONE, &print_cap, "Output to stdout the backend capabilities in JSON format and exit", NULL}, | 117 | + { "fd", 'f', 0, G_OPTION_ARG_INT, &socket_fd, |
163 | + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be more verbose in output", NULL}, | 118 | + "Specify file-descriptor of the backend, don't use with --socket-path", |
119 | + "FD" }, | ||
120 | + { "device-list", 'l', 0, G_OPTION_ARG_STRING, &device_list, | ||
121 | + "List of i2c-dev bus and attached devices", "I2C Devices" }, | ||
122 | + { "print-capabilities", 'c', 0, G_OPTION_ARG_NONE, &print_cap, | ||
123 | + "Output to stdout the backend capabilities in JSON format and exit", | ||
124 | + NULL}, | ||
125 | + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, | ||
126 | + "Be more verbose in output", NULL}, | ||
164 | + { NULL } | 127 | + { NULL } |
165 | +}; | 128 | +}; |
166 | + | 129 | + |
167 | + | 130 | + |
168 | +/* I2c helpers */ | 131 | +/* I2c helpers */ |
... | ... | ||
175 | + } | 138 | + } |
176 | + g_string_append_printf(s, "%x ", bytes[i]); | 139 | + g_string_append_printf(s, "%x ", bytes[i]); |
177 | + } | 140 | + } |
178 | +} | 141 | +} |
179 | + | 142 | + |
180 | +static void vi2c_dump_msg(struct i2c_msg *msg) | 143 | +static void vi2c_dump_msg(struct i2c_msg *msgs, size_t count) |
181 | +{ | 144 | +{ |
182 | + g_autoptr(GString) s = g_string_new("\nI2c request: "); | 145 | + int32_t i; |
183 | + | 146 | + |
184 | + g_string_append_printf(s, "addr: %x\n", msg->addr); | 147 | + for (i = 0; i < count; i++) { |
185 | + g_string_append_printf(s, "transfer len: %x\n", msg->len); | 148 | + g_autoptr(GString) s = g_string_new("\nI2c request: "); |
186 | + | 149 | + |
187 | + g_string_append_printf(s, "%s: ", msg->flags & I2C_M_RD ? "Data read" : | 150 | + g_string_append_printf(s, "addr: %x\n", msgs[i].addr); |
188 | + "Data Written"); | 151 | + g_string_append_printf(s, "transfer len: %x\n", msgs[i].len); |
189 | + fmt_bytes(s, (uint8_t *)msg->buf, msg->len); | 152 | + |
190 | + g_string_append_printf(s, "\n"); | 153 | + g_string_append_printf(s, "%s: ", msgs[i].flags & I2C_M_RD ? |
191 | + | 154 | + "Data read" : "Data Written"); |
192 | + g_debug("%s: %s", __func__, s->str); | 155 | + fmt_bytes(s, (uint8_t *)msgs[i].buf, msgs[i].len); |
156 | + g_string_append_printf(s, "\n"); | ||
157 | + | ||
158 | + g_debug("%s: %s", __func__, s->str); | ||
159 | + } | ||
193 | +} | 160 | +} |
194 | + | 161 | + |
195 | +static int vi2c_map_adapters(VuI2c *i2c) | 162 | +static int vi2c_map_adapters(VuI2c *i2c) |
196 | +{ | 163 | +{ |
197 | + VI2cAdapter *adapter; | 164 | + VI2cAdapter *adapter; |
... | ... | ||
200 | + /* | 167 | + /* |
201 | + * Flatten the map for client address and adapter to the array: | 168 | + * Flatten the map for client address and adapter to the array: |
202 | + * | 169 | + * |
203 | + * adapter_map[MAX_I2C_VDEV]: | 170 | + * adapter_map[MAX_I2C_VDEV]: |
204 | + * | 171 | + * |
205 | + * Adapter | adapter2 | none | adapter1 | adapter3 | none | none| (val) | 172 | + * Adapter | adapter2 | none | adapter1 | adapter3 | none| (val) |
206 | + * |----------|-------|----------|----------|------|-----| | 173 | + * |----------|-------|----------|----------|-----| |
207 | + * Slave Address | addr 1 | none | addr 2 | addr 3 | none | none| (idx) | 174 | + * Slave Address | addr 1 | none | addr 2 | addr 3 | none| (idx) |
208 | + * |<-----------------------MAX_I2C_VDEV---------------->| | 175 | + * |<-----------------------MAX_I2C_VDEV---------------->| |
209 | + */ | 176 | + */ |
210 | + for (i = 0; i < i2c->adapter_num; i++) { | 177 | + for (i = 0; i < i2c->adapter_num; i++) { |
211 | + adapter = i2c->adapter[i]; | 178 | + adapter = i2c->adapter[i]; |
212 | + | 179 | + |
... | ... | ||
230 | + return 0; | 197 | + return 0; |
231 | +} | 198 | +} |
232 | + | 199 | + |
233 | +static VI2cAdapter *vi2c_find_adapter(VuI2c *i2c, uint16_t addr) | 200 | +static VI2cAdapter *vi2c_find_adapter(VuI2c *i2c, uint16_t addr) |
234 | +{ | 201 | +{ |
235 | + int32_t idx; | 202 | + if (addr < MAX_I2C_VDEV && (i2c->adapter_map[addr] != 0)) { |
236 | + | 203 | + return i2c->adapter[i2c->adapter_map[addr] - 1]; |
237 | + if (addr < MAX_I2C_VDEV && ((idx = i2c->adapter_map[addr]) != 0)) { | ||
238 | + return i2c->adapter[idx - 1]; | ||
239 | + } | 204 | + } |
240 | + | 205 | + |
241 | + return NULL; | 206 | + return NULL; |
242 | +} | 207 | +} |
243 | + | 208 | + |
244 | +static bool vi2c_client_access_ok(VI2cAdapter *adapter, uint16_t addr) | 209 | +static bool vi2c_set_client_addr(VI2cAdapter *adapter, uint16_t addr) |
245 | +{ | 210 | +{ |
246 | + if (ioctl(adapter->fd, I2C_SLAVE, addr) < 0) { | 211 | + if (ioctl(adapter->fd, I2C_SLAVE, addr) < 0) { |
247 | + if (errno == EBUSY) { | 212 | + if (errno == EBUSY) { |
248 | + g_printerr("client device %x is busy!\n", addr); | 213 | + g_printerr("client device %x is busy!\n", addr); |
249 | + } else { | 214 | + } else { |
... | ... | ||
257 | +static void vi2c_remove_adapters(VuI2c *i2c) | 222 | +static void vi2c_remove_adapters(VuI2c *i2c) |
258 | +{ | 223 | +{ |
259 | + VI2cAdapter *adapter; | 224 | + VI2cAdapter *adapter; |
260 | + int32_t i; | 225 | + int32_t i; |
261 | + | 226 | + |
262 | + for (i = 0; i < i2c->adapter_num; i++) { | 227 | + for (i = 0; i < MAX_I2C_ADAPTER; i++) { |
263 | + adapter = i2c->adapter[i]; | 228 | + adapter = i2c->adapter[i]; |
264 | + if (!adapter) { | 229 | + if (!adapter) { |
265 | + break; | 230 | + break; |
266 | + } | 231 | + } |
267 | + | 232 | + |
... | ... | ||
277 | +static VI2cAdapter *vi2c_create_adapter(int32_t bus, uint16_t client_addr[], | 242 | +static VI2cAdapter *vi2c_create_adapter(int32_t bus, uint16_t client_addr[], |
278 | + int32_t n_client) | 243 | + int32_t n_client) |
279 | +{ | 244 | +{ |
280 | + VI2cAdapter *adapter; | 245 | + VI2cAdapter *adapter; |
281 | + char path[20]; | 246 | + char path[20]; |
247 | + uint64_t funcs; | ||
282 | + int32_t fd, i; | 248 | + int32_t fd, i; |
283 | + | 249 | + |
284 | + if (bus < 0) | 250 | + if (bus < 0) { |
285 | + return NULL; | 251 | + return NULL; |
252 | + } | ||
286 | + | 253 | + |
287 | + adapter = g_malloc0(sizeof(*adapter)); | 254 | + adapter = g_malloc0(sizeof(*adapter)); |
288 | + if (!adapter) { | 255 | + if (!adapter) { |
289 | + g_printerr("failed to alloc adapter"); | 256 | + g_printerr("failed to alloc adapter"); |
290 | + return NULL; | 257 | + return NULL; |
... | ... | ||
300 | + } | 267 | + } |
301 | + | 268 | + |
302 | + adapter->fd = fd; | 269 | + adapter->fd = fd; |
303 | + adapter->bus = bus; | 270 | + adapter->bus = bus; |
304 | + | 271 | + |
272 | + if (ioctl(fd, I2C_FUNCS, &funcs) < 0) { | ||
273 | + g_printerr("virtio_i2c: failed to get functionality %s: %d\n", path, | ||
274 | + errno); | ||
275 | + goto close_fd; | ||
276 | + } | ||
277 | + | ||
278 | + if (funcs & I2C_FUNC_I2C) { | ||
279 | + adapter->smbus = false; | ||
280 | + } else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) { | ||
281 | + adapter->smbus = true; | ||
282 | + } else { | ||
283 | + g_printerr("virtio_i2c: invalid functionality %lx\n", funcs); | ||
284 | + goto close_fd; | ||
285 | + } | ||
286 | + | ||
305 | + for (i = 0; i < n_client; i++) { | 287 | + for (i = 0; i < n_client; i++) { |
306 | + if (client_addr[i]) { | 288 | + if (client_addr[i]) { |
307 | + if (!vi2c_client_access_ok(adapter, client_addr[i])) { | 289 | + if (!vi2c_set_client_addr(adapter, client_addr[i])) { |
308 | + goto fail; | 290 | + goto close_fd; |
309 | + } | 291 | + } |
310 | + | 292 | + |
311 | + if (adapter->clients[client_addr[i]]) { | 293 | + if (adapter->clients[client_addr[i]]) { |
312 | + g_printerr("client addr 0x%x repeat, not allowed.\n", client_addr[i]); | 294 | + g_printerr("client addr 0x%x repeat, not allowed.\n", |
313 | + goto fail; | 295 | + client_addr[i]); |
296 | + goto close_fd; | ||
314 | + } | 297 | + } |
315 | + | 298 | + |
316 | + adapter->clients[client_addr[i]] = true; | 299 | + adapter->clients[client_addr[i]] = true; |
317 | + if (verbose) { | 300 | + if (verbose) { |
318 | + g_print("Added client 0x%x to bus %u\n", client_addr[i], bus); | 301 | + g_print("Added client 0x%x to bus %u\n", client_addr[i], bus); |
319 | + } | 302 | + } |
320 | + } | 303 | + } |
321 | + } | 304 | + } |
305 | + | ||
306 | + if (verbose) { | ||
307 | + g_print("Added adapter: bus: %d, func %s\n", bus, | ||
308 | + adapter->smbus ? "smbus" : "i2c"); | ||
309 | + } | ||
322 | + return adapter; | 310 | + return adapter; |
323 | + | 311 | + |
312 | +close_fd: | ||
313 | + close(fd); | ||
324 | +fail: | 314 | +fail: |
325 | + g_free(adapter); | 315 | + g_free(adapter); |
326 | + return NULL; | 316 | + return NULL; |
327 | +} | 317 | +} |
328 | + | 318 | + |
... | ... | ||
331 | + * | 321 | + * |
332 | + * <bus>:<client_addr>[:<client_addr>], | 322 | + * <bus>:<client_addr>[:<client_addr>], |
333 | + * [<bus>:<client_addr>[:<client_addr>]] | 323 | + * [<bus>:<client_addr>[:<client_addr>]] |
334 | + * | 324 | + * |
335 | + * bus (dec): adatper bus number. | 325 | + * bus (dec): adatper bus number. |
336 | + * e.g. 2 for /dev/i2c-2 | 326 | + * e.g. 2 for /dev/i2c-2 |
337 | + * client_addr (hex): address for client device | 327 | + * client_addr (hex): address for client device |
338 | + * e.g. 0x1C or 1C | 328 | + * e.g. 0x1C or 1C |
339 | + * | 329 | + * |
340 | + * Example: --device-list="2:0x1c:0x20,3:0x10:0x2c" | 330 | + * Example: --device-list="2:0x1c:0x20,3:0x10:0x2c" |
341 | + * | 331 | + * |
342 | + * Note: client address can not repeat. | 332 | + * Note: client address can not repeat. |
343 | + */ | 333 | + */ |
344 | +static int vi2c_parse(VuI2c *i2c) | 334 | +static int32_t vi2c_parse(VuI2c *i2c) |
345 | +{ | 335 | +{ |
346 | + uint16_t client_addr[MAX_I2C_VDEV]; | 336 | + uint16_t client_addr[MAX_I2C_VDEV]; |
347 | + int32_t n_adapter = 0, n_client; | 337 | + int32_t n_adapter = 0, n_client; |
348 | + int64_t addr, bus; | 338 | + int64_t addr, bus; |
349 | + const char *cp, *t; | 339 | + const char *cp, *t; |
350 | + | 340 | + |
351 | + while (device_list) { | 341 | + while (device_list) { |
352 | + /* Read <bus>:<client_addr>[:<client_addr>] entries one by one */ | 342 | + /* Read <bus>:<client_addr>[:<client_addr>] entries one by one */ |
353 | + cp = strsep(&device_list, ","); | 343 | + cp = strsep(&device_list, ","); |
354 | + | 344 | + |
355 | + if (!cp || *cp =='\0') { | 345 | + if (!cp || *cp == '\0') { |
356 | + break; | 346 | + break; |
357 | + } | 347 | + } |
358 | + | 348 | + |
359 | + if (n_adapter == MAX_I2C_ADAPTER) { | 349 | + if (n_adapter == MAX_I2C_ADAPTER) { |
360 | + g_printerr("too many adapter (%d), only support %d \n", n_adapter, | 350 | + g_printerr("too many adapter (%d), only support %d\n", n_adapter, |
361 | + MAX_I2C_ADAPTER); | 351 | + MAX_I2C_ADAPTER); |
362 | + goto out; | 352 | + goto out; |
363 | + } | 353 | + } |
364 | + | 354 | + |
365 | + if (qemu_strtol(cp, &t, 10, &bus) || bus < 0) { | 355 | + if (qemu_strtol(cp, &t, 10, &bus) || bus < 0) { |
... | ... | ||
369 | + | 359 | + |
370 | + cp = t; | 360 | + cp = t; |
371 | + n_client = 0; | 361 | + n_client = 0; |
372 | + | 362 | + |
373 | + /* Parse clients <client_addr>[:<client_addr>] entries one by one */ | 363 | + /* Parse clients <client_addr>[:<client_addr>] entries one by one */ |
374 | + while (cp != NULL && *cp !='\0') { | 364 | + while (cp != NULL && *cp != '\0') { |
375 | + if (*cp == ':') | 365 | + if (*cp == ':') { |
376 | + cp++; | 366 | + cp++; |
367 | + } | ||
377 | + | 368 | + |
378 | + if (n_client == MAX_I2C_VDEV) { | 369 | + if (n_client == MAX_I2C_VDEV) { |
379 | + g_printerr("too many devices (%d), only support %d \n", | 370 | + g_printerr("too many devices (%d), only support %d\n", |
380 | + n_client, MAX_I2C_VDEV); | 371 | + n_client, MAX_I2C_VDEV); |
381 | + goto out; | 372 | + goto out; |
382 | + } | 373 | + } |
383 | + | 374 | + |
384 | + if (qemu_strtol(cp, &t, 16, &addr) || addr < 0 || addr > MAX_I2C_VDEV) { | 375 | + if (qemu_strtol(cp, &t, 16, &addr) || |
376 | + addr < 0 || addr > MAX_I2C_VDEV) { | ||
385 | + g_printerr("Invalid address %s : %lx\n", cp, addr); | 377 | + g_printerr("Invalid address %s : %lx\n", cp, addr); |
386 | + goto out; | 378 | + goto out; |
387 | + } | 379 | + } |
388 | + | 380 | + |
389 | + client_addr[n_client++] = addr; | 381 | + client_addr[n_client++] = addr; |
390 | + cp = t; | 382 | + cp = t; |
391 | + if (verbose) { | 383 | + if (verbose) { |
392 | + g_print("i2c adapter %ld:0x%lx\n", bus, addr); | 384 | + g_print("i2c adapter %ld:0x%lx\n", bus, addr); |
393 | + } | 385 | + } |
394 | + } | 386 | + } |
395 | + | 387 | + |
396 | + i2c->adapter[n_adapter] = vi2c_create_adapter(bus, client_addr, n_client); | 388 | + i2c->adapter[n_adapter] = vi2c_create_adapter(bus, client_addr, |
397 | + if (!i2c->adapter[n_adapter]) | 389 | + n_client); |
390 | + if (!i2c->adapter[n_adapter]) { | ||
398 | + goto out; | 391 | + goto out; |
392 | + } | ||
393 | + | ||
399 | + n_adapter++; | 394 | + n_adapter++; |
400 | + } | 395 | + } |
401 | + | 396 | + |
402 | + if (!n_adapter) { | 397 | + if (!n_adapter) { |
403 | + g_printerr("Failed to add any adapters\n"); | 398 | + g_printerr("Failed to add any adapters\n"); |
... | ... | ||
413 | +out: | 408 | +out: |
414 | + vi2c_remove_adapters(i2c); | 409 | + vi2c_remove_adapters(i2c); |
415 | + return -1; | 410 | + return -1; |
416 | +} | 411 | +} |
417 | + | 412 | + |
418 | +static uint8_t vi2c_xfer(VuDev *dev, struct i2c_msg *msg) | 413 | +static int32_t i2c_xfer(VI2cAdapter *adapter, struct i2c_msg *msgs, |
414 | + size_t count) | ||
415 | +{ | ||
416 | + struct i2c_rdwr_ioctl_data data; | ||
417 | + | ||
418 | + data.nmsgs = count; | ||
419 | + data.msgs = msgs; | ||
420 | + | ||
421 | + return ioctl(adapter->fd, I2C_RDWR, &data); | ||
422 | +} | ||
423 | + | ||
424 | +/* | ||
425 | + * Based on Linux's drivers/i2c/i2c-core-smbus.c:i2c_smbus_xfer_emulated(). This | ||
426 | + * function tries to reverse what Linux does, only support basic modes (up to | ||
427 | + * word transfer). | ||
428 | + */ | ||
429 | +static int32_t smbus_xfer(VI2cAdapter *adapter, struct i2c_msg *msgs, | ||
430 | + size_t count) | ||
431 | +{ | ||
432 | + union i2c_smbus_data data; | ||
433 | + struct i2c_smbus_ioctl_data smbus_data = { .data = &data }; | ||
434 | + int32_t ret; | ||
435 | + | ||
436 | + smbus_data.command = msgs[0].buf[0]; | ||
437 | + | ||
438 | + switch (count) { | ||
439 | + case 1: | ||
440 | + if (msgs[0].flags & I2C_M_RD) { | ||
441 | + if (msgs[0].len > 1) { | ||
442 | + g_printerr("Incorrect message length for read operation: %d\n", | ||
443 | + msgs[0].len); | ||
444 | + return -1; | ||
445 | + } | ||
446 | + smbus_data.read_write = I2C_SMBUS_READ; | ||
447 | + } else { | ||
448 | + smbus_data.read_write = I2C_SMBUS_WRITE; | ||
449 | + } | ||
450 | + | ||
451 | + if (!msgs[0].len) { | ||
452 | + smbus_data.size = I2C_SMBUS_QUICK; | ||
453 | + smbus_data.data = NULL; | ||
454 | + } else if (msgs[0].len == 1) { | ||
455 | + smbus_data.size = I2C_SMBUS_BYTE; | ||
456 | + if (smbus_data.read_write == I2C_SMBUS_WRITE) { | ||
457 | + smbus_data.data = NULL; | ||
458 | + } | ||
459 | + } else if (msgs[0].len == 2) { | ||
460 | + smbus_data.size = I2C_SMBUS_BYTE_DATA; | ||
461 | + data.byte = msgs[0].buf[1]; | ||
462 | + } else if (msgs[0].len == 3) { | ||
463 | + smbus_data.size = I2C_SMBUS_WORD_DATA; | ||
464 | + data.word = msgs[0].buf[1] | (msgs[0].buf[2] << 8); | ||
465 | + } else { | ||
466 | + g_printerr("Message length not supported for write operation: %d\n", | ||
467 | + msgs[0].len); | ||
468 | + return -1; | ||
469 | + } | ||
470 | + | ||
471 | + break; | ||
472 | + case 2: | ||
473 | + if ((msgs[0].flags & I2C_M_RD) || !(msgs[1].flags & I2C_M_RD) || | ||
474 | + (msgs[0].len != 1) || (msgs[1].len > 2)) { | ||
475 | + g_printerr("Expecting a valid read smbus transfer: %ld: %d: %d\n", | ||
476 | + count, msgs[0].len, msgs[1].len); | ||
477 | + return -1; | ||
478 | + } | ||
479 | + | ||
480 | + smbus_data.read_write = I2C_SMBUS_READ; | ||
481 | + smbus_data.size = msgs[1].len == 1 ? | ||
482 | + I2C_SMBUS_BYTE_DATA : I2C_SMBUS_WORD_DATA; | ||
483 | + break; | ||
484 | + default: | ||
485 | + g_printerr("Invalid number of messages for smbus xfer: %ld\n", count); | ||
486 | + return -1; | ||
487 | + } | ||
488 | + | ||
489 | + if (verbose) { | ||
490 | + g_print("SMBUS command: %x: %x: %x\n", smbus_data.read_write, | ||
491 | + smbus_data.command, smbus_data.size); | ||
492 | + } | ||
493 | + | ||
494 | + ret = ioctl(adapter->fd, I2C_SMBUS, &smbus_data); | ||
495 | + if (ret < 0) { | ||
496 | + return ret; | ||
497 | + } | ||
498 | + | ||
499 | + if (smbus_data.read_write == I2C_SMBUS_WRITE) { | ||
500 | + return 0; | ||
501 | + } | ||
502 | + | ||
503 | + switch (smbus_data.size) { | ||
504 | + case I2C_SMBUS_BYTE: | ||
505 | + msgs[0].buf[0] = data.byte; | ||
506 | + break; | ||
507 | + case I2C_SMBUS_BYTE_DATA: | ||
508 | + msgs[1].buf[0] = data.byte; | ||
509 | + break; | ||
510 | + case I2C_SMBUS_WORD_DATA: | ||
511 | + msgs[1].buf[0] = data.word & 0xff; | ||
512 | + msgs[1].buf[1] = data.word >> 8; | ||
513 | + break; | ||
514 | + } | ||
515 | + | ||
516 | + return 0; | ||
517 | +} | ||
518 | + | ||
519 | +static uint8_t vi2c_xfer(VuDev *dev, struct i2c_msg *msgs, size_t count) | ||
419 | +{ | 520 | +{ |
420 | + VuI2c *i2c = container_of(dev, VuI2c, dev.parent); | 521 | + VuI2c *i2c = container_of(dev, VuI2c, dev.parent); |
421 | + struct i2c_rdwr_ioctl_data data; | ||
422 | + VI2cAdapter *adapter; | 522 | + VI2cAdapter *adapter; |
423 | + | 523 | + int8_t ret; |
424 | + adapter = vi2c_find_adapter(i2c, msg->addr); | 524 | + |
525 | + /* Assume that adapter should be same for all messages sent together */ | ||
526 | + adapter = vi2c_find_adapter(i2c, msgs[0].addr); | ||
425 | + if (!adapter) { | 527 | + if (!adapter) { |
426 | + g_printerr("Failed to find adapter for address: %x\n", msg->addr); | 528 | + g_printerr("Failed to find adapter for address: %x\n", msgs[0].addr); |
427 | + return VIRTIO_I2C_MSG_ERR; | 529 | + return VIRTIO_I2C_MSG_ERR; |
428 | + } | 530 | + } |
429 | + | 531 | + |
430 | + data.nmsgs = 1; | 532 | + /* Set client's address */ |
431 | + data.msgs = msg; | 533 | + if (!vi2c_set_client_addr(adapter, msgs[0].addr)) { |
432 | + | ||
433 | + if (ioctl(adapter->fd, I2C_RDWR, &data) < 0) { | ||
434 | + g_printerr("Failed to transfer data to address %x : %d\n", msg->addr, errno); | ||
435 | + return VIRTIO_I2C_MSG_ERR; | 534 | + return VIRTIO_I2C_MSG_ERR; |
436 | + } | 535 | + } |
437 | + | 536 | + |
537 | + if (adapter->smbus) { | ||
538 | + ret = smbus_xfer(adapter, msgs, count); | ||
539 | + } else { | ||
540 | + ret = i2c_xfer(adapter, msgs, count); | ||
541 | + } | ||
542 | + | ||
543 | + if (ret < 0) { | ||
544 | + g_printerr("Failed to transfer data to address %x : %d\n", | ||
545 | + msgs[0].addr, errno); | ||
546 | + return VIRTIO_I2C_MSG_ERR; | ||
547 | + } | ||
548 | + | ||
438 | + if (verbose) { | 549 | + if (verbose) { |
439 | + vi2c_dump_msg(msg); | 550 | + vi2c_dump_msg(msgs, count); |
440 | + } | 551 | + } |
441 | + | 552 | + |
442 | + return VIRTIO_I2C_MSG_OK; | 553 | + return VIRTIO_I2C_MSG_OK; |
443 | +} | 554 | +} |
444 | + | ||
445 | + | 555 | + |
446 | +/* Virtio helpers */ | 556 | +/* Virtio helpers */ |
447 | +static uint64_t vi2c_get_features(VuDev *dev) | 557 | +static uint64_t vi2c_get_features(VuDev *dev) |
448 | +{ | 558 | +{ |
449 | + if (verbose) { | 559 | + if (verbose) { |
... | ... | ||
462 | +} | 572 | +} |
463 | + | 573 | + |
464 | +static void vi2c_handle_ctrl(VuDev *dev, int qidx) | 574 | +static void vi2c_handle_ctrl(VuDev *dev, int qidx) |
465 | +{ | 575 | +{ |
466 | + VuVirtq *vq = vu_get_queue(dev, qidx); | 576 | + VuVirtq *vq = vu_get_queue(dev, qidx); |
467 | + struct i2c_msg msg; | ||
468 | + struct virtio_i2c_out_hdr *out_hdr; | 577 | + struct virtio_i2c_out_hdr *out_hdr; |
469 | + struct virtio_i2c_in_hdr *in_hdr; | 578 | + size_t i, count = 0, len, in_hdr_len; |
470 | + bool fail_next = false; | 579 | + struct i2c_msg *msgs; |
471 | + size_t len, in_hdr_len; | 580 | + VuVirtqElement *elem; |
472 | + | 581 | + uint8_t status; |
473 | + for (;;) { | 582 | + struct { |
583 | + struct virtio_i2c_in_hdr *in_hdr; | ||
474 | + VuVirtqElement *elem; | 584 | + VuVirtqElement *elem; |
475 | + | 585 | + size_t size; |
586 | + } *info; | ||
587 | + | ||
588 | + /* Count number of messages */ | ||
589 | + do { | ||
590 | + elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); | ||
591 | + } while (elem && ++count); | ||
592 | + | ||
593 | + if (!count) { | ||
594 | + if (verbose) { | ||
595 | + g_printerr("Virtqueue can't have 0 elements\n"); | ||
596 | + } | ||
597 | + return; | ||
598 | + } | ||
599 | + | ||
600 | + /* Rewind everything, now that we have counted the number of messages */ | ||
601 | + vu_queue_rewind(dev, vq, count); | ||
602 | + | ||
603 | + if (verbose) { | ||
604 | + g_print("Received %ld messages in virtqueue\n", count); | ||
605 | + } | ||
606 | + | ||
607 | + /* Allocate memory for msgs and info */ | ||
608 | + msgs = g_malloc0_n(count, sizeof(*msgs) + sizeof(*info)); | ||
609 | + if (!msgs) { | ||
610 | + g_printerr("failed to allocate space for messages\n"); | ||
611 | + return; | ||
612 | + } | ||
613 | + info = (void *)(msgs + count); | ||
614 | + | ||
615 | + for (i = 0; i < count; i++) { | ||
476 | + elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); | 616 | + elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); |
477 | + if (!elem) { | 617 | + if (!elem) { |
478 | + break; | 618 | + g_printerr("Failed to pop element: %ld : %ld\n", i, count); |
479 | + } | 619 | + goto out; |
620 | + } | ||
621 | + info[i].elem = elem; | ||
622 | + info[i].size = sizeof(*info[i].in_hdr); | ||
480 | + | 623 | + |
481 | + g_debug("%s: got queue (in %d, out %d)", __func__, elem->in_num, | 624 | + g_debug("%s: got queue (in %d, out %d)", __func__, elem->in_num, |
482 | + elem->out_num); | 625 | + elem->out_num); |
483 | + | 626 | + |
484 | + /* Validate size of out header */ | 627 | + /* Validate size of "out" header */ |
485 | + if (elem->out_sg[0].iov_len != sizeof(*out_hdr)) { | 628 | + if (elem->out_sg[0].iov_len != sizeof(*out_hdr)) { |
486 | + g_warning("%s: Invalid out hdr %zu : %zu\n", __func__, | 629 | + g_warning("%s: Invalid out hdr %zu : %zu\n", __func__, |
487 | + elem->out_sg[0].iov_len, sizeof(*out_hdr)); | 630 | + elem->out_sg[0].iov_len, sizeof(*out_hdr)); |
488 | + continue; | 631 | + goto out; |
489 | + } | 632 | + } |
490 | + | 633 | + |
491 | + out_hdr = elem->out_sg[0].iov_base; | 634 | + out_hdr = elem->out_sg[0].iov_base; |
492 | + | 635 | + |
493 | + /* Bit 0 is reserved in virtio spec */ | 636 | + /* Bit 0 is reserved in virtio spec */ |
494 | + msg.addr = out_hdr->addr >> 1; | 637 | + msgs[i].addr = le16toh(out_hdr->addr) >> 1; |
495 | + | 638 | + |
496 | + /* Read Operation */ | 639 | + /* Read Operation */ |
497 | + if (elem->out_num == 1 && elem->in_num == 2) { | 640 | + if (elem->out_num == 1 && elem->in_num == 2) { |
498 | + len = elem->in_sg[0].iov_len; | 641 | + len = elem->in_sg[0].iov_len; |
499 | + if (!len) { | 642 | + if (!len) { |
500 | + g_warning("%s: Read buffer length can't be zero\n", __func__); | 643 | + g_warning("%s: Read buffer length can't be zero\n", __func__); |
501 | + continue; | 644 | + goto out; |
502 | + } | 645 | + } |
503 | + | 646 | + |
504 | + msg.buf = elem->in_sg[0].iov_base; | 647 | + msgs[i].buf = elem->in_sg[0].iov_base; |
505 | + msg.flags = I2C_M_RD; | 648 | + msgs[i].flags = I2C_M_RD; |
506 | + msg.len = len; | 649 | + msgs[i].len = len; |
507 | + | 650 | + |
508 | + in_hdr = elem->in_sg[1].iov_base; | 651 | + info[i].in_hdr = elem->in_sg[1].iov_base; |
652 | + info[i].size += len; | ||
509 | + in_hdr_len = elem->in_sg[1].iov_len; | 653 | + in_hdr_len = elem->in_sg[1].iov_len; |
510 | + } else if (elem->out_num == 2 && elem->in_num == 1) { | 654 | + } else if (elem->out_num == 2 && elem->in_num == 1) { |
511 | + /* Write Operation */ | 655 | + /* Write Operation */ |
512 | + len = elem->out_sg[1].iov_len; | 656 | + len = elem->out_sg[1].iov_len; |
513 | + if (!len) { | 657 | + if (!len) { |
514 | + g_warning("%s: Write buffer length can't be zero\n", __func__); | 658 | + g_warning("%s: Write buffer length can't be zero\n", __func__); |
515 | + continue; | 659 | + goto out; |
516 | + } | 660 | + } |
517 | + | 661 | + |
518 | + msg.buf = elem->out_sg[1].iov_base; | 662 | + msgs[i].buf = elem->out_sg[1].iov_base; |
519 | + msg.flags = 0; | 663 | + msgs[i].flags = 0; |
520 | + msg.len = len; | 664 | + msgs[i].len = len; |
521 | + | 665 | + |
522 | + in_hdr = elem->in_sg[0].iov_base; | 666 | + info[i].in_hdr = elem->in_sg[0].iov_base; |
523 | + in_hdr_len = elem->in_sg[0].iov_len; | 667 | + in_hdr_len = elem->in_sg[0].iov_len; |
524 | + len = 0; | ||
525 | + } else { | 668 | + } else { |
526 | + g_warning("%s: Transfer type not supported (in %d, out %d)\n", | 669 | + g_warning("%s: Transfer type not supported (in %d, out %d)\n", |
527 | + __func__, elem->in_num, elem->out_num); | 670 | + __func__, elem->in_num, elem->out_num); |
528 | + continue; | 671 | + goto out; |
529 | + } | 672 | + } |
530 | + | 673 | + |
531 | + /* Validate size of in header */ | 674 | + /* Validate size of "in" header */ |
532 | + if (in_hdr_len != sizeof(*in_hdr)) { | 675 | + if (in_hdr_len != sizeof(*(info[i].in_hdr))) { |
533 | + g_warning("%s: Invalid in hdr %zu : %zu\n", __func__, in_hdr_len, | 676 | + g_warning("%s: Invalid in hdr %zu : %zu\n", __func__, in_hdr_len, |
534 | + sizeof(*in_hdr)); | 677 | + sizeof(*(info[i].in_hdr))); |
535 | + continue; | 678 | + goto out; |
536 | + } | 679 | + } |
537 | + | 680 | + } |
538 | + in_hdr->status = fail_next ? VIRTIO_I2C_MSG_ERR : vi2c_xfer(dev, &msg); | 681 | + |
539 | + if (in_hdr->status == VIRTIO_I2C_MSG_ERR) { | 682 | + status = vi2c_xfer(dev, msgs, count); |
540 | + /* We need to fail remaining transfers as well */ | 683 | + |
541 | + fail_next = out_hdr->flags & VIRTIO_I2C_FLAGS_FAIL_NEXT; | 684 | + for (i = 0; i < count; i++) { |
542 | + } | 685 | + info[i].in_hdr->status = status; |
543 | + | 686 | + vu_queue_push(dev, vq, info[i].elem, info[i].size); |
544 | + vu_queue_push(dev, vq, elem, len + sizeof(*in_hdr)); | ||
545 | + } | 687 | + } |
546 | + | 688 | + |
547 | + vu_queue_notify(dev, vq); | 689 | + vu_queue_notify(dev, vq); |
690 | + | ||
691 | +out: | ||
692 | + g_free(msgs); | ||
548 | +} | 693 | +} |
549 | + | 694 | + |
550 | +static void | 695 | +static void |
551 | +vi2c_queue_set_started(VuDev *dev, int qidx, bool started) | 696 | +vi2c_queue_set_started(VuDev *dev, int qidx, bool started) |
552 | +{ | 697 | +{ |
... | ... | ||
623 | + g_autoptr(GSocket) socket = NULL; | 768 | + g_autoptr(GSocket) socket = NULL; |
624 | + VuI2c i2c = {0}; | 769 | + VuI2c i2c = {0}; |
625 | + | 770 | + |
626 | + context = g_option_context_new("vhost-user emulation of I2C device"); | 771 | + context = g_option_context_new("vhost-user emulation of I2C device"); |
627 | + g_option_context_add_main_entries(context, options, "vhost-user-i2c"); | 772 | + g_option_context_add_main_entries(context, options, "vhost-user-i2c"); |
628 | + if (!g_option_context_parse(context, &argc, &argv, &error)) | 773 | + if (!g_option_context_parse(context, &argc, &argv, &error)) { |
629 | + { | ||
630 | + g_printerr("option parsing failed: %s\n", error->message); | 774 | + g_printerr("option parsing failed: %s\n", error->message); |
631 | + exit(1); | 775 | + exit(1); |
632 | + } | 776 | + } |
633 | + | 777 | + |
634 | + if (print_cap) { | 778 | + if (print_cap) { |
... | ... | ||
643 | + | 787 | + |
644 | + if (verbose) { | 788 | + if (verbose) { |
645 | + g_log_set_handler(NULL, G_LOG_LEVEL_MASK, g_log_default_handler, NULL); | 789 | + g_log_set_handler(NULL, G_LOG_LEVEL_MASK, g_log_default_handler, NULL); |
646 | + g_setenv("G_MESSAGES_DEBUG", "all", true); | 790 | + g_setenv("G_MESSAGES_DEBUG", "all", true); |
647 | + } else { | 791 | + } else { |
648 | + g_log_set_handler(NULL, | 792 | + g_log_set_handler(NULL, G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL | |
649 | + G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_ERROR, | 793 | + G_LOG_LEVEL_ERROR, g_log_default_handler, NULL); |
650 | + g_log_default_handler, NULL); | ||
651 | + } | 794 | + } |
652 | + | 795 | + |
653 | + /* | 796 | + /* |
654 | + * Now create a vhost-user socket that we will receive messages | 797 | + * Now create a vhost-user socket that we will receive messages |
655 | + * on. Once we have our handler set up we can enter the glib main | 798 | + * on. Once we have our handler set up we can enter the glib main |
656 | + * loop. | 799 | + * loop. |
657 | + */ | 800 | + */ |
658 | + if (socket_path) { | 801 | + if (socket_path) { |
659 | + g_autoptr(GSocketAddress) addr = g_unix_socket_address_new(socket_path); | 802 | + g_autoptr(GSocketAddress) addr = g_unix_socket_address_new(socket_path); |
660 | + g_autoptr(GSocket) bind_socket = g_socket_new(G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM, | 803 | + g_autoptr(GSocket) bind_socket = g_socket_new(G_SOCKET_FAMILY_UNIX, |
661 | + G_SOCKET_PROTOCOL_DEFAULT, &error); | 804 | + G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, &error); |
662 | + | 805 | + |
663 | + if (!g_socket_bind(bind_socket, addr, false, &error)) { | 806 | + if (!g_socket_bind(bind_socket, addr, false, &error)) { |
664 | + g_printerr("Failed to bind to socket at %s (%s).\n", | 807 | + g_printerr("Failed to bind to socket at %s (%s).\n", |
665 | + socket_path, error->message); | 808 | + socket_path, error->message); |
666 | + exit(EXIT_FAILURE); | 809 | + exit(EXIT_FAILURE); |
... | ... | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
34 | +----------- | 34 | +----------- |
35 | + | 35 | + |
36 | +This program is a vhost-user backend that emulates a VirtIO I2C bus. | 36 | +This program is a vhost-user backend that emulates a VirtIO I2C bus. |
37 | +This program takes the layout of the i2c bus and its devices on the host | 37 | +This program takes the layout of the i2c bus and its devices on the host |
38 | +OS and then talks to them via the /dev/i2c-X interface when a request | 38 | +OS and then talks to them via the /dev/i2c-X interface when a request |
39 | +comes from the guest OS for an I2C device. | 39 | +comes from the guest OS for an I2C or SMBUS device. |
40 | + | 40 | + |
41 | +This program is designed to work with QEMU's ``-device | 41 | +This program is designed to work with QEMU's ``-device |
42 | +vhost-user-i2c-pci`` but should work with any virtual machine monitor | 42 | +vhost-user-i2c-pci`` but should work with any virtual machine monitor |
43 | +(VMM) that supports vhost-user. See the Examples section below. | 43 | +(VMM) that supports vhost-user. See the Examples section below. |
44 | + | 44 | + |
... | ... | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
7 | 7 | ||
8 | diff --git a/MAINTAINERS b/MAINTAINERS | 8 | diff --git a/MAINTAINERS b/MAINTAINERS |
9 | index XXXXXXX..XXXXXXX 100644 | 9 | index XXXXXXX..XXXXXXX 100644 |
10 | --- a/MAINTAINERS | 10 | --- a/MAINTAINERS |
11 | +++ b/MAINTAINERS | 11 | +++ b/MAINTAINERS |
12 | @@ -XXX,XX +XXX,XX @@ F: hw/virtio/virtio-mem-pci.h | 12 | @@ -XXX,XX +XXX,XX @@ F: docs/interop/vhost-user-gpu.rst |
13 | F: hw/virtio/virtio-mem-pci.c | 13 | F: contrib/vhost-user-gpu |
14 | F: include/hw/virtio/virtio-mem.h | 14 | F: hw/display/vhost-user-* |
15 | 15 | ||
16 | +virtio-i2c | 16 | +vhost-user-i2c |
17 | +M: Viresh Kumar <viresh.kumar@linaro.org> | 17 | +M: Viresh Kumar <viresh.kumar@linaro.org> |
18 | +S: Supported | 18 | +S: Supported |
19 | +F: docs/tools/vhost-user-i2c.rst | 19 | +F: docs/tools/vhost-user-i2c.rst |
20 | +F: hw/virtio/vhost-user-i2c.c | 20 | +F: hw/virtio/vhost-user-i2c.c |
21 | +F: hw/virtio/vhost-user-i2c-pci.c | 21 | +F: hw/virtio/vhost-user-i2c-pci.c |
22 | +F: include/hw/virtio/vhost-user-i2c.h | 22 | +F: include/hw/virtio/vhost-user-i2c.h |
23 | +F: tools/vhost-user-i2c/* | 23 | +F: tools/vhost-user-i2c/* |
24 | + | 24 | + |
25 | nvme | 25 | Cirrus VGA |
26 | M: Keith Busch <kbusch@kernel.org> | 26 | M: Gerd Hoffmann <kraxel@redhat.com> |
27 | M: Klaus Jensen <its@irrelevant.dk> | 27 | S: Odd Fixes |
28 | -- | 28 | -- |
29 | 2.25.0.rc1.19.g042ed3e048af | 29 | 2.25.0.rc1.19.g042ed3e048af |
30 | 30 | ||
31 | 31 | diff view generated by jsdifflib |