xref: /openbmc/qemu/hw/virtio/vhost-user-i2c.c (revision efee71c8)
1 /*
2  * Vhost-user i2c virtio device
3  *
4  * Copyright (c) 2021 Viresh Kumar <viresh.kumar@linaro.org>
5  *
6  * SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 
9 #include "qemu/osdep.h"
10 #include "qapi/error.h"
11 #include "hw/qdev-properties.h"
12 #include "hw/virtio/virtio-bus.h"
13 #include "hw/virtio/vhost-user-i2c.h"
14 #include "qemu/error-report.h"
15 #include "standard-headers/linux/virtio_ids.h"
16 
17 /* Remove this once the header is updated in Linux kernel */
18 #ifndef VIRTIO_ID_I2C_ADAPTER
19 #define VIRTIO_ID_I2C_ADAPTER                34
20 #endif
21 
22 static void vu_i2c_start(VirtIODevice *vdev)
23 {
24     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
25     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
26     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
27     int ret, i;
28 
29     if (!k->set_guest_notifiers) {
30         error_report("binding does not support guest notifiers");
31         return;
32     }
33 
34     ret = vhost_dev_enable_notifiers(&i2c->vhost_dev, vdev);
35     if (ret < 0) {
36         error_report("Error enabling host notifiers: %d", -ret);
37         return;
38     }
39 
40     ret = k->set_guest_notifiers(qbus->parent, i2c->vhost_dev.nvqs, true);
41     if (ret < 0) {
42         error_report("Error binding guest notifier: %d", -ret);
43         goto err_host_notifiers;
44     }
45 
46     i2c->vhost_dev.acked_features = vdev->guest_features;
47 
48     ret = vhost_dev_start(&i2c->vhost_dev, vdev);
49     if (ret < 0) {
50         error_report("Error starting vhost-user-i2c: %d", -ret);
51         goto err_guest_notifiers;
52     }
53 
54     /*
55      * guest_notifier_mask/pending not used yet, so just unmask
56      * everything here. virtio-pci will do the right thing by
57      * enabling/disabling irqfd.
58      */
59     for (i = 0; i < i2c->vhost_dev.nvqs; i++) {
60         vhost_virtqueue_mask(&i2c->vhost_dev, vdev, i, false);
61     }
62 
63     return;
64 
65 err_guest_notifiers:
66     k->set_guest_notifiers(qbus->parent, i2c->vhost_dev.nvqs, false);
67 err_host_notifiers:
68     vhost_dev_disable_notifiers(&i2c->vhost_dev, vdev);
69 }
70 
71 static void vu_i2c_stop(VirtIODevice *vdev)
72 {
73     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
74     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
75     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
76     int ret;
77 
78     if (!k->set_guest_notifiers) {
79         return;
80     }
81 
82     vhost_dev_stop(&i2c->vhost_dev, vdev);
83 
84     ret = k->set_guest_notifiers(qbus->parent, i2c->vhost_dev.nvqs, false);
85     if (ret < 0) {
86         error_report("vhost guest notifier cleanup failed: %d", ret);
87         return;
88     }
89 
90     vhost_dev_disable_notifiers(&i2c->vhost_dev, vdev);
91 }
92 
93 static void vu_i2c_set_status(VirtIODevice *vdev, uint8_t status)
94 {
95     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
96     bool should_start = status & VIRTIO_CONFIG_S_DRIVER_OK;
97 
98     if (!vdev->vm_running) {
99         should_start = false;
100     }
101 
102     if (i2c->vhost_dev.started == should_start) {
103         return;
104     }
105 
106     if (should_start) {
107         vu_i2c_start(vdev);
108     } else {
109         vu_i2c_stop(vdev);
110     }
111 }
112 
113 static uint64_t vu_i2c_get_features(VirtIODevice *vdev,
114                                     uint64_t requested_features, Error **errp)
115 {
116     /* No feature bits used yet */
117     return requested_features;
118 }
119 
120 static void vu_i2c_handle_output(VirtIODevice *vdev, VirtQueue *vq)
121 {
122     /*
123      * Not normally called; it's the daemon that handles the queue;
124      * however virtio's cleanup path can call this.
125      */
126 }
127 
128 static void vu_i2c_guest_notifier_mask(VirtIODevice *vdev, int idx, bool mask)
129 {
130     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
131 
132     vhost_virtqueue_mask(&i2c->vhost_dev, vdev, idx, mask);
133 }
134 
135 static bool vu_i2c_guest_notifier_pending(VirtIODevice *vdev, int idx)
136 {
137     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
138 
139     return vhost_virtqueue_pending(&i2c->vhost_dev, idx);
140 }
141 
142 static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserI2C *i2c)
143 {
144     vhost_user_cleanup(&i2c->vhost_user);
145     virtio_delete_queue(i2c->vq);
146     virtio_cleanup(vdev);
147     g_free(i2c->vhost_dev.vqs);
148     i2c->vhost_dev.vqs = NULL;
149 }
150 
151 static int vu_i2c_connect(DeviceState *dev)
152 {
153     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
154     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
155 
156     if (i2c->connected) {
157         return 0;
158     }
159     i2c->connected = true;
160 
161     /* restore vhost state */
162     if (virtio_device_started(vdev, vdev->status)) {
163         vu_i2c_start(vdev);
164     }
165 
166     return 0;
167 }
168 
169 static void vu_i2c_disconnect(DeviceState *dev)
170 {
171     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
172     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
173 
174     if (!i2c->connected) {
175         return;
176     }
177     i2c->connected = false;
178 
179     if (i2c->vhost_dev.started) {
180         vu_i2c_stop(vdev);
181     }
182 }
183 
184 static void vu_i2c_event(void *opaque, QEMUChrEvent event)
185 {
186     DeviceState *dev = opaque;
187     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
188     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
189 
190     switch (event) {
191     case CHR_EVENT_OPENED:
192         if (vu_i2c_connect(dev) < 0) {
193             qemu_chr_fe_disconnect(&i2c->chardev);
194             return;
195         }
196         break;
197     case CHR_EVENT_CLOSED:
198         vu_i2c_disconnect(dev);
199         break;
200     case CHR_EVENT_BREAK:
201     case CHR_EVENT_MUX_IN:
202     case CHR_EVENT_MUX_OUT:
203         /* Ignore */
204         break;
205     }
206 }
207 
208 static void vu_i2c_device_realize(DeviceState *dev, Error **errp)
209 {
210     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
211     VHostUserI2C *i2c = VHOST_USER_I2C(dev);
212     int ret;
213 
214     if (!i2c->chardev.chr) {
215         error_setg(errp, "vhost-user-i2c: missing chardev");
216         return;
217     }
218 
219     if (!vhost_user_init(&i2c->vhost_user, &i2c->chardev, errp)) {
220         return;
221     }
222 
223     virtio_init(vdev, "vhost-user-i2c", VIRTIO_ID_I2C_ADAPTER, 0);
224 
225     i2c->vhost_dev.nvqs = 1;
226     i2c->vq = virtio_add_queue(vdev, 4, vu_i2c_handle_output);
227     i2c->vhost_dev.vqs = g_new0(struct vhost_virtqueue, i2c->vhost_dev.nvqs);
228 
229     ret = vhost_dev_init(&i2c->vhost_dev, &i2c->vhost_user,
230                          VHOST_BACKEND_TYPE_USER, 0, errp);
231     if (ret < 0) {
232         do_vhost_user_cleanup(vdev, i2c);
233     }
234 
235     qemu_chr_fe_set_handlers(&i2c->chardev, NULL, NULL, vu_i2c_event, NULL,
236                              dev, NULL, true);
237 }
238 
239 static void vu_i2c_device_unrealize(DeviceState *dev)
240 {
241     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
242     VHostUserI2C *i2c = VHOST_USER_I2C(dev);
243 
244     /* This will stop vhost backend if appropriate. */
245     vu_i2c_set_status(vdev, 0);
246     vhost_dev_cleanup(&i2c->vhost_dev);
247     do_vhost_user_cleanup(vdev, i2c);
248 }
249 
250 static const VMStateDescription vu_i2c_vmstate = {
251     .name = "vhost-user-i2c",
252     .unmigratable = 1,
253 };
254 
255 static Property vu_i2c_properties[] = {
256     DEFINE_PROP_CHR("chardev", VHostUserI2C, chardev),
257     DEFINE_PROP_END_OF_LIST(),
258 };
259 
260 static void vu_i2c_class_init(ObjectClass *klass, void *data)
261 {
262     DeviceClass *dc = DEVICE_CLASS(klass);
263     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
264 
265     device_class_set_props(dc, vu_i2c_properties);
266     dc->vmsd = &vu_i2c_vmstate;
267     set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
268     vdc->realize = vu_i2c_device_realize;
269     vdc->unrealize = vu_i2c_device_unrealize;
270     vdc->get_features = vu_i2c_get_features;
271     vdc->set_status = vu_i2c_set_status;
272     vdc->guest_notifier_mask = vu_i2c_guest_notifier_mask;
273     vdc->guest_notifier_pending = vu_i2c_guest_notifier_pending;
274 }
275 
276 static const TypeInfo vu_i2c_info = {
277     .name = TYPE_VHOST_USER_I2C,
278     .parent = TYPE_VIRTIO_DEVICE,
279     .instance_size = sizeof(VHostUserI2C),
280     .class_init = vu_i2c_class_init,
281 };
282 
283 static void vu_i2c_register_types(void)
284 {
285     type_register_static(&vu_i2c_info);
286 }
287 
288 type_init(vu_i2c_register_types)
289