summaryrefslogtreecommitdiffstats
path: root/arch/um/drivers
diff options
context:
space:
mode:
authorJohannes Berg <johannes.berg@intel.com>2020-12-15 10:52:24 +0100
committerRichard Weinberger <richard@nod.at>2021-02-12 21:24:27 +0100
commitc8177aba37cac6b6dd0e5511fde9fc2d9e7f2f38 (patch)
tree0918ea45e4ccfb6dfded0ec20264450cf23de1a4 /arch/um/drivers
parent9b84512cfe601759f66ee594b2d5aa07788251ea (diff)
downloadlinux-c8177aba37cac6b6dd0e5511fde9fc2d9e7f2f38.tar.bz2
um: time-travel: rework interrupt handling in ext mode
In external time-travel mode, where time is controlled via the controller application socket, interrupt handling is a little tricky. For example on virtio, the following happens: * we receive a message (that requires an ACK) on the vhost-user socket * we add a time-travel event to handle the interrupt (this causes communication on the time socket) * we ACK the original vhost-user message * we then handle the interrupt once the event is triggered This protocol ensures that the sender of the interrupt only continues to run in the simulation when the time-travel event has been added. So far, this was only done in the virtio driver, but it was actually wrong, because only virtqueue interrupts were handled this way, and config change interrupts were handled immediately. Additionally, the messages were actually handled in the real Linux interrupt handler, but Linux interrupt handlers are part of the simulation and shouldn't run while there's no time event. To really do this properly and only handle all kinds of interrupts in the time-travel event when we are scheduled to run in the simulation, rework this to plug in to the lower interrupt layers in UML directly: Add a um_request_irq_tt() function that let's a time-travel aware driver request an interrupt with an additional timetravel_handler() that is called outside of the context of the simulation, to handle the message only. It then adds an event to the time-travel calendar if necessary, and no "real" Linux code runs outside of the time simulation. This also hooks in with suspend/resume properly now, since this new timetravel_handler() can run while Linux is suspended and interrupts are disabled, and decide to wake up (or not) the system based on the message it received. Importantly in this case, it ACKs the message before the system even resumes and interrupts are re-enabled, thus allowing the simulation to progress properly. Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: Richard Weinberger <richard@nod.at>
Diffstat (limited to 'arch/um/drivers')
-rw-r--r--arch/um/drivers/virtio_uml.c92
1 files changed, 48 insertions, 44 deletions
diff --git a/arch/um/drivers/virtio_uml.c b/arch/um/drivers/virtio_uml.c
index 9db9d00b2dc2..bda3ab468938 100644
--- a/arch/um/drivers/virtio_uml.c
+++ b/arch/um/drivers/virtio_uml.c
@@ -55,16 +55,14 @@ struct virtio_uml_device {
u64 protocol_features;
u8 status;
u8 registered:1;
+
+ u8 config_changed_irq:1;
+ uint64_t vq_irq_vq_map;
};
struct virtio_uml_vq_info {
int kick_fd, call_fd;
char name[32];
-#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
- struct virtqueue *vq;
- vq_callback_t *callback;
- struct time_travel_event defer;
-#endif
bool suspended;
};
@@ -351,9 +349,9 @@ static void vhost_user_reply(struct virtio_uml_device *vu_dev,
rc, size);
}
-static irqreturn_t vu_req_interrupt(int irq, void *data)
+static irqreturn_t vu_req_read_message(struct virtio_uml_device *vu_dev,
+ struct time_travel_event *ev)
{
- struct virtio_uml_device *vu_dev = data;
struct virtqueue *vq;
int response = 1;
struct {
@@ -371,14 +369,14 @@ static irqreturn_t vu_req_interrupt(int irq, void *data)
switch (msg.msg.header.request) {
case VHOST_USER_SLAVE_CONFIG_CHANGE_MSG:
- virtio_config_changed(&vu_dev->vdev);
+ vu_dev->config_changed_irq = true;
response = 0;
break;
case VHOST_USER_SLAVE_VRING_CALL:
virtio_device_for_each_vq((&vu_dev->vdev), vq) {
if (vq->index == msg.msg.payload.vring_state.index) {
response = 0;
- vring_interrupt(0 /* ignored */, vq);
+ vu_dev->vq_irq_vq_map |= BIT_ULL(vq->index);
break;
}
}
@@ -392,12 +390,45 @@ static irqreturn_t vu_req_interrupt(int irq, void *data)
msg.msg.header.request);
}
+ if (ev)
+ time_travel_add_irq_event(ev);
+
if (msg.msg.header.flags & VHOST_USER_FLAG_NEED_REPLY)
vhost_user_reply(vu_dev, &msg.msg, response);
return IRQ_HANDLED;
}
+static irqreturn_t vu_req_interrupt(int irq, void *data)
+{
+ struct virtio_uml_device *vu_dev = data;
+ irqreturn_t ret = IRQ_HANDLED;
+
+ if (!um_irq_timetravel_handler_used())
+ ret = vu_req_read_message(vu_dev, NULL);
+
+ if (vu_dev->vq_irq_vq_map) {
+ struct virtqueue *vq;
+
+ virtio_device_for_each_vq((&vu_dev->vdev), vq) {
+ if (vu_dev->vq_irq_vq_map & BIT_ULL(vq->index))
+ vring_interrupt(0 /* ignored */, vq);
+ }
+ vu_dev->vq_irq_vq_map = 0;
+ } else if (vu_dev->config_changed_irq) {
+ virtio_config_changed(&vu_dev->vdev);
+ vu_dev->config_changed_irq = false;
+ }
+
+ return ret;
+}
+
+static void vu_req_interrupt_comm_handler(int irq, int fd, void *data,
+ struct time_travel_event *ev)
+{
+ vu_req_read_message(data, ev);
+}
+
static int vhost_user_init_slave_req(struct virtio_uml_device *vu_dev)
{
int rc, req_fds[2];
@@ -408,9 +439,10 @@ static int vhost_user_init_slave_req(struct virtio_uml_device *vu_dev)
return rc;
vu_dev->req_fd = req_fds[0];
- rc = um_request_irq(UM_IRQ_ALLOC, vu_dev->req_fd, IRQ_READ,
- vu_req_interrupt, IRQF_SHARED,
- vu_dev->pdev->name, vu_dev);
+ rc = um_request_irq_tt(UM_IRQ_ALLOC, vu_dev->req_fd, IRQ_READ,
+ vu_req_interrupt, IRQF_SHARED,
+ vu_dev->pdev->name, vu_dev,
+ vu_req_interrupt_comm_handler);
if (rc < 0)
goto err_close;
@@ -882,23 +914,6 @@ out:
return rc;
}
-#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
-static void vu_defer_irq_handle(struct time_travel_event *d)
-{
- struct virtio_uml_vq_info *info;
-
- info = container_of(d, struct virtio_uml_vq_info, defer);
- info->callback(info->vq);
-}
-
-static void vu_defer_irq_callback(struct virtqueue *vq)
-{
- struct virtio_uml_vq_info *info = vq->priv;
-
- time_travel_add_irq_event(&info->defer);
-}
-#endif
-
static struct virtqueue *vu_setup_vq(struct virtio_device *vdev,
unsigned index, vq_callback_t *callback,
const char *name, bool ctx)
@@ -918,18 +933,6 @@ static struct virtqueue *vu_setup_vq(struct virtio_device *vdev,
snprintf(info->name, sizeof(info->name), "%s.%d-%s", pdev->name,
pdev->id, name);
-#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
- /*
- * When we get an interrupt, we must bounce it through the simulation
- * calendar (the time-travel=ext:... socket).
- */
- if (time_travel_mode == TT_MODE_EXTERNAL && callback) {
- info->callback = callback;
- callback = vu_defer_irq_callback;
- time_travel_set_event_fn(&info->defer, vu_defer_irq_handle);
- }
-#endif
-
vq = vring_create_virtqueue(index, num, PAGE_SIZE, vdev, true, true,
ctx, vu_notify, callback, info->name);
if (!vq) {
@@ -938,9 +941,6 @@ static struct virtqueue *vu_setup_vq(struct virtio_device *vdev,
}
vq->priv = info;
num = virtqueue_get_vring_size(vq);
-#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
- info->vq = vq;
-#endif
if (vu_dev->protocol_features &
BIT_ULL(VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS)) {
@@ -999,6 +999,10 @@ static int vu_find_vqs(struct virtio_device *vdev, unsigned nvqs,
int i, queue_idx = 0, rc;
struct virtqueue *vq;
+ /* not supported for now */
+ if (WARN_ON(nvqs > 64))
+ return -EINVAL;
+
rc = vhost_user_set_mem_table(vu_dev);
if (rc)
return rc;