Continuing from the previous article:
The virtio_balloon_pci_realize function was analyzed in detail last time. Finally, it is mentioned that the object_property_set function is called in the last step of the virtio_balloon_pci_realize function, which leads to the execution of the virtio_device_realize function. This time we will analyze the virtio_device_realize function.
For ease of understanding, the source code of the virtio_device_realize function is posted again, in hw/virtio/virtio.c, as follows:
static void virtio_device_realize(DeviceState *dev, Error **errp)
{
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(dev);
Error *err = NULL;
/* Devices should either use vmsd or the load/save methods */
assert(!vdc->vmsd || !vdc->load);
if (vdc->realize != NULL) {
vdc->realize(dev, &err);
if (err != NULL) {
error_propagate(errp, err);
return;
}
}
virtio_bus_device_plugged(vdev, &err);
if (err != NULL) {
error_propagate(errp, err);
vdc->unrealize(dev);
return;
}
vdev->listener.commit = virtio_memory_listener_commit;
vdev->listener.name = "virtio";
memory_listener_register(&vdev->listener, vdev->dma_as);
QTAILQ_INSERT_TAIL(&virtio_list, vdev, next);
}
The virtio_device_realize function is actually a general function, which is the realization function of the abstract device of type TYPE_VIRTIO_DEVICE. All virtio devices will call this function during initialization.
(1) The virtio_device_realize function first gets the class to which the virtio device belongs. The code snippet is as follows:
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(dev);
(2) Then call the realize function of the specific class, which is the virtio_balloon_device_realize function for the virtio balloon device. The code snippet is as follows:
if (vdc->realize != NULL) {
vdc->realize(dev, &err);
if (err != NULL) {
error_propagate(errp, err);
return;
}
}
(3) Next, call the virtio_bus_device_plugged function to connect the virtio device to the virtio bus. The code snippet is as follows:
virtio_bus_device_plugged(vdev, &err);
if (err != NULL) {
error_propagate(errp, err);
vdc->unrealize(dev);
return;
}
(4) Subsequently, call the memory_listener_register function to register the memory listener. The code snippet is as follows:
memory_listener_register(&vdev->listener, vdev->dma_as);
The memory_listener_register function is declared in include/exec/memory.h, and its prototype is as follows:
/**
* memory_listener_register: register callbacks to be called when memory
* sections are mapped or unmapped into an address
* space
*
* @listener: an object containing the callbacks to be called
* @filter: if non-%NULL, only regions in this address space will be observed
*/
void memory_listener_register(MemoryListener *listener, AddressSpace *filter);
This function registers the listener (here is vdev->listener) into the filter address space (here is vdev->dma_as). When the topology of the filter address space changes, all the links on its linked list will be called. listener, calls the relevant callback function.
(5) Finally, call QTAILQ_INSERT_TAIL() to insert vdev into the end of virtio_list. The code snippet is as follows:
QTAILQ_INSERT_TAIL(&virtio_list, vdev, next);
QTAILQ_INSERT_TAIL is a macro used to insert elements into the tail of the queue. QTAIL_INSERT_TAIL is defined as follows (in include/qemu/queue.h):
#define QTAILQ_INSERT_TAIL(head, elm, field) do { \
(elm)->field.tqe_next = NULL; \
(elm)->field.tqe_circ.tql_prev = (head)->tqh_circ.tql_prev; \
(head)->tqh_circ.tql_prev->tql_next = (elm); \
(head)->tqh_circ.tql_prev = &(elm)->field.tqe_circ; \
} while (/*CONSTCOND*/0)
Therefore, the above code finally expands to:
do { \
(vdev)->next.tqe_next = NULL; \
(vdev)->next.tqe_circ.tql_prev = (&virtio_list)->tqh_circ.tql_prev; \
(&virtio_list)->tqh_circ.tql_prev->tql_next = (vdev); \
(&virtio_list)->tqh_circ.tql_prev = &(vdev)->next.tqe_circ; \
} while (/*CONSTCOND*/0)
If you want to know what happens next, let’s look at the breakdown in the next chapter.