xref: /openbmc/qemu/hw/virtio/vhost-user-i2c.c (revision 9f22020b)
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 static const int feature_bits[] = {
18     VIRTIO_I2C_F_ZERO_LENGTH_REQUEST,
19     VHOST_INVALID_FEATURE_BIT
20 };
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     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
117 
118     virtio_add_feature(&requested_features, VIRTIO_I2C_F_ZERO_LENGTH_REQUEST);
119     return vhost_get_features(&i2c->vhost_dev, feature_bits, requested_features);
120 }
121 
122 static void vu_i2c_handle_output(VirtIODevice *vdev, VirtQueue *vq)
123 {
124     /*
125      * Not normally called; it's the daemon that handles the queue;
126      * however virtio's cleanup path can call this.
127      */
128 }
129 
130 static void vu_i2c_guest_notifier_mask(VirtIODevice *vdev, int idx, bool mask)
131 {
132     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
133 
134     vhost_virtqueue_mask(&i2c->vhost_dev, vdev, idx, mask);
135 }
136 
137 static bool vu_i2c_guest_notifier_pending(VirtIODevice *vdev, int idx)
138 {
139     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
140 
141     return vhost_virtqueue_pending(&i2c->vhost_dev, idx);
142 }
143 
144 static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserI2C *i2c)
145 {
146     vhost_user_cleanup(&i2c->vhost_user);
147     virtio_delete_queue(i2c->vq);
148     virtio_cleanup(vdev);
149     g_free(i2c->vhost_dev.vqs);
150     i2c->vhost_dev.vqs = NULL;
151 }
152 
153 static int vu_i2c_connect(DeviceState *dev)
154 {
155     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
156     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
157 
158     if (i2c->connected) {
159         return 0;
160     }
161     i2c->connected = true;
162 
163     /* restore vhost state */
164     if (virtio_device_started(vdev, vdev->status)) {
165         vu_i2c_start(vdev);
166     }
167 
168     return 0;
169 }
170 
171 static void vu_i2c_disconnect(DeviceState *dev)
172 {
173     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
174     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
175 
176     if (!i2c->connected) {
177         return;
178     }
179     i2c->connected = false;
180 
181     if (i2c->vhost_dev.started) {
182         vu_i2c_stop(vdev);
183     }
184 }
185 
186 static void vu_i2c_event(void *opaque, QEMUChrEvent event)
187 {
188     DeviceState *dev = opaque;
189     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
190     VHostUserI2C *i2c = VHOST_USER_I2C(vdev);
191 
192     switch (event) {
193     case CHR_EVENT_OPENED:
194         if (vu_i2c_connect(dev) < 0) {
195             qemu_chr_fe_disconnect(&i2c->chardev);
196             return;
197         }
198         break;
199     case CHR_EVENT_CLOSED:
200         vu_i2c_disconnect(dev);
201         break;
202     case CHR_EVENT_BREAK:
203     case CHR_EVENT_MUX_IN:
204     case CHR_EVENT_MUX_OUT:
205         /* Ignore */
206         break;
207     }
208 }
209 
210 static void vu_i2c_device_realize(DeviceState *dev, Error **errp)
211 {
212     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
213     VHostUserI2C *i2c = VHOST_USER_I2C(dev);
214     int ret;
215 
216     if (!i2c->chardev.chr) {
217         error_setg(errp, "vhost-user-i2c: missing chardev");
218         return;
219     }
220 
221     if (!vhost_user_init(&i2c->vhost_user, &i2c->chardev, errp)) {
222         return;
223     }
224 
225     virtio_init(vdev, VIRTIO_ID_I2C_ADAPTER, 0);
226 
227     i2c->vhost_dev.nvqs = 1;
228     i2c->vq = virtio_add_queue(vdev, 4, vu_i2c_handle_output);
229     i2c->vhost_dev.vqs = g_new0(struct vhost_virtqueue, i2c->vhost_dev.nvqs);
230 
231     ret = vhost_dev_init(&i2c->vhost_dev, &i2c->vhost_user,
232                          VHOST_BACKEND_TYPE_USER, 0, errp);
233     if (ret < 0) {
234         do_vhost_user_cleanup(vdev, i2c);
235     }
236 
237     qemu_chr_fe_set_handlers(&i2c->chardev, NULL, NULL, vu_i2c_event, NULL,
238                              dev, NULL, true);
239 }
240 
241 static void vu_i2c_device_unrealize(DeviceState *dev)
242 {
243     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
244     VHostUserI2C *i2c = VHOST_USER_I2C(dev);
245 
246     /* This will stop vhost backend if appropriate. */
247     vu_i2c_set_status(vdev, 0);
248     vhost_dev_cleanup(&i2c->vhost_dev);
249     do_vhost_user_cleanup(vdev, i2c);
250 }
251 
252 static const VMStateDescription vu_i2c_vmstate = {
253     .name = "vhost-user-i2c",
254     .unmigratable = 1,
255 };
256 
257 static Property vu_i2c_properties[] = {
258     DEFINE_PROP_CHR("chardev", VHostUserI2C, chardev),
259     DEFINE_PROP_END_OF_LIST(),
260 };
261 
262 static void vu_i2c_class_init(ObjectClass *klass, void *data)
263 {
264     DeviceClass *dc = DEVICE_CLASS(klass);
265     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
266 
267     device_class_set_props(dc, vu_i2c_properties);
268     dc->vmsd = &vu_i2c_vmstate;
269     set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
270     vdc->realize = vu_i2c_device_realize;
271     vdc->unrealize = vu_i2c_device_unrealize;
272     vdc->get_features = vu_i2c_get_features;
273     vdc->set_status = vu_i2c_set_status;
274     vdc->guest_notifier_mask = vu_i2c_guest_notifier_mask;
275     vdc->guest_notifier_pending = vu_i2c_guest_notifier_pending;
276 }
277 
278 static const TypeInfo vu_i2c_info = {
279     .name = TYPE_VHOST_USER_I2C,
280     .parent = TYPE_VIRTIO_DEVICE,
281     .instance_size = sizeof(VHostUserI2C),
282     .class_init = vu_i2c_class_init,
283 };
284 
285 static void vu_i2c_register_types(void)
286 {
287     type_register_static(&vu_i2c_info);
288 }
289 
290 type_init(vu_i2c_register_types)
291