/**
 * FILE: virtio-demo-pci.c
 * ------------------------
 * PCI bindings for the virtio-demo device.
 * - Used if <devicename>-pci is specified.
 * - Unused if <devicename>-device is specified.
 * Common backend is used by both pci and mmio
 */

#include "qemu/osdep.h"

#include "hw/qdev-properties.h"
#include "hw/virtio/virtio-demo.h"
#include "qemu/module.h"
#include "hw/virtio/virtio-pci.h"
#include "qom/object.h"

typedef struct VirtIODemoPCI VirtIODemoPCI;

// enables usage of a new VIRTIO_DEMO_PCI() macro to 
// create a reference to a VirtIODemoPCI struct.
#define TYPE_VIRTIO_DEMO_PCI "virtio-demo-pci-base"
DECLARE_INSTANCE_CHECKER(VirtIODemoPCI, VIRTIO_DEMO_PCI,
                         TYPE_VIRTIO_DEMO_PCI)


// template for any virtio-pci binding.
// Virtio PCI devices must extend the VirtIOPCIProxy
// base class.
// https://www.qemu.org/docs/master/devel/virtio-backends.html
struct VirtIODemoPCI {
    VirtIOPCIProxy parent_obj;

    // The .vdev is an object that represents a device backend
    // for this device. Every virtio device backend is registered on
    // the "virtio-bus", which itself is part of the pci bus.
    // - I suspect the virtio-bus is mmio-based, which would make sense
    // considering virtqueue notifs are handled by the device backend
    // and not the pci-bindings file.
    // -----------------------------------------------------
    // Without pci bindings file, your device would be forced to use
    // the virtio-mmio bus, which is only implemented in the
    //  `microvm`machine. 
    VirtIODemo vdev;
};


// Every VirtIOPCIProxy object will have
// these properties, accessible via 
// object_property_find().
// - also accessible via 
static Property virtio_demo_pci_properties[] = {
    DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
                    VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true),
    DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors,
                       DEV_NVECTORS_UNSPECIFIED),
    DEFINE_PROP_END_OF_LIST(),
};

// This function is called by QEMU main code, starting from qdev_device_add()
// and triggers the realization of the device backend, 
// - Realization could involve several virtio-specific actions such
// as initializing virtqueues an their notification handlers.
static void virtio_demo_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) {
    VirtIODemoPCI *dev = VIRTIO_DEMO_PCI(vpci_dev);

    // not sure why cast to devicestate is valid
    DeviceState *vdev = DEVICE(&dev->vdev); 

    vpci_dev->class_code = PCI_CLASS_OTHERS;

    // nvectors is added as a property, not sure why.
    // not even sure what a vector is...
    if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) {
        vpci_dev->nvectors = 2;
    }

    // @imp: eventually calls the realize of the backend device
    qdev_realize(vdev, BUS(&vpci_dev->bus), errp); 
}


// Declares the virtual methods and some attributes 
// that belong to this class (and possibly parents). 
// - Essentially helps to define 
// the DEVICE_CLASS->PCI_DEVICE_CLASS->VIRTIO_PCI_CLASS 
// inherited attributes and methods.
static void virtio_demo_pci_class_init(ObjectClass *klass, void *data) {
    // create direct references to all the 
    // heirarchy classes to avoid an
    // obj->parent->parent chain.
    DeviceClass *dc = DEVICE_CLASS(klass);
    VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
    PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);

    // Register the properties
    device_class_set_props(dc, virtio_demo_pci_properties);

    // method to realize this pci device,
    // which also realizes this pci device's backend.
    k->realize = virtio_demo_pci_realize; 

    // ensure that any device in this hierarchy is 
    // classed as _MISC. (see qdev-core.h)
    set_bit(DEVICE_CATEGORY_MISC, dc->categories);

    // standard PCI device identifiers.
    pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
    pcidev_k->revision = VIRTIO_PCI_ABI_VERSION;
    pcidev_k->class_id = PCI_CLASS_OTHERS;

    // Technically not required when disable-legacy=on.
    // Modern devices autogenerate the PCI device ID.
    // - below val will be overwritten by QEMU.
    pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_DEMO; 
}

// QEMU cli_create_devices() triggers a instance creation
// for the device type passed. Here also, the idea is to create a
// object of .vdev type
static void virtio_demo_pci_instance_init(Object *obj) {
    VirtIODemoPCI *dev = VIRTIO_DEMO_PCI(obj);
    
    // creates the virtio backend for the virtio-bus
    virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
                                TYPE_VIRTIO_DEMO);

}


static const VirtioPCIDeviceTypeInfo virtio_demo_pci_info = {
    .parent = TYPE_VIRTIO_PCI, // see virtio-pci.h. Allows reg on the pci bus.
    .base_name = TYPE_VIRTIO_DEMO_PCI,
    .generic_name = "virtio-demo-pci",
    .transitional_name = "virtio-demo-pci-transitional",
    .non_transitional_name = "virtio-demo-pci-non-transitional",
    .instance_size = sizeof(VirtIODemoPCI),

    .class_init = virtio_demo_pci_class_init,
    .instance_init = virtio_demo_pci_instance_init,
};

// Boilerplate to register a qemu type
static void virtio_demo_pci_register(void) {
    virtio_pci_types_register(&virtio_demo_pci_info);
}

type_init(virtio_demo_pci_register);




