qemu總線系統(二) sysbus

qemu 支持的主版以Intel 440FX PMC(PCI and Memory Controller)爲北橋芯片,PIIX(PCI ISA Xcelerator)爲南橋芯片構成相應的芯片組。

在這裏插入圖片描述
440FX PMC的基本結構圖如下所示:

北橋芯片PMC用於連接主板上的高速設備,向上提供了連接處理器的Host總線接口,可以連接多個處理器,向下則主要提供了連接內存DRAM的接口和連接PCI總線系統的PCI總線接口,通過該PCI root port擴展出整個PCI設備樹,包括PIIX南橋芯片。

qemu的sysbus所模擬的總線就是上圖中的host bus。 host bus向上連接cpu,向下連接北橋芯片, 北橋芯片提供內存控制器和host bridge功能, host bridge是一個pci bridge,下面連接pci根總線, pci根總線通過南橋芯片連接isa總線用於兼容老式設備。

今天主要介紹sysbus的實現。

在qemu初始化的時候首先創建cpu,cpu創建的過程就會創建host總線,在創建設備的時候如果沒有指定bus,就會將設備掛在到sysbus總線。具體調動棧爲
qdev_create -> qdev_try_create -> sysbus_get_default

sysbus_get_default 如下

BusState *sysbus_get_default(void)
{
    if (!main_system_bus) {
        main_system_bus_create();
    }
    return main_system_bus;
}

如果還沒有創建main_sys_bus 則使用main_system_bus_create來創建。

static void main_system_bus_create(void)
{
    /* assign main_system_bus before qbus_create_inplace()
     * in order to make "if (bus != sysbus_get_default())" work */
    main_system_bus = g_malloc0(system_bus_info.instance_size);
    qbus_create_inplace(main_system_bus, system_bus_info.instance_size,
                        TYPE_SYSTEM_BUS, NULL, "main-system-bus");
    OBJECT(main_system_bus)->free = g_free;
    object_property_add_child(container_get(qdev_get_machine(),
                                            "/unattached"),
                              "sysbus", OBJECT(main_system_bus), NULL);
}

函數很簡單。調用qbus_create_inplace 創建總線。併爲總線設置父節點,將sysbus添加到/machine/unattached 規範路徑下。

關於qbus_create_inplace可以參考qemu總線系統一 -----bus
qom規範路徑和屬性系統可以參考qemu2的qom系統分析(二)規範路徑和屬性

sysbus的typeinfo爲system_bus_info,

static const TypeInfo system_bus_info = {
    .name = TYPE_SYSTEM_BUS,
    .parent = TYPE_BUS,
    .instance_size = sizeof(BusState),
    .class_init = system_bus_class_init,
};

類額外的初始化不多,只有system_bus_class_init函數,在類初始化時執行。實例也使用的BusState沒有做額外擴展。

static void system_bus_class_init(ObjectClass *klass, void *data)
{
    BusClass *k = BUS_CLASS(klass);

    k->print_dev = sysbus_dev_print;
    k->get_fw_dev_path = sysbus_get_fw_dev_path;
}

只是設置了sysbus_dev_print 函數用於調試打印設備。get_fw_dev_path 則用於獲取設備的of規範名稱。

再來看下支持掛在在sysbus下的設備是啥樣的

static const TypeInfo sysbus_device_type_info = {
   .name = TYPE_SYS_BUS_DEVICE,
   .parent = TYPE_DEVICE,
   .instance_size = sizeof(SysBusDevice),
   .abstract = true,
   .class_size = sizeof(SysBusDeviceClass),
   .class_init = sysbus_device_class_init,
};

這是一個抽象類,具體的類爲SysBusDeviceClass,實例爲SysBusDevice。來看下

typedef struct SysBusDeviceClass {
    /*< private >*/
    DeviceClass parent_class;
    /*< public >*/

    int (*init)(SysBusDevice *dev);

    /*
     * Let the sysbus device format its own non-PIO, non-MMIO unit address.
     *
     * Sometimes a class of SysBusDevices has neither MMIO nor PIO resources,
     * yet instances of it would like to distinguish themselves, in
     * OpenFirmware device paths, from other instances of the same class on the
     * sysbus. For that end we expose this callback.
     *
     * The implementation is not supposed to change *@dev, or incur other
     * observable change.
     *
     * The function returns a dynamically allocated string. On error, NULL
     * should be returned; the unit address portion of the OFW node will be
     * omitted then. (This is not considered a fatal error.)
     */
    char *(*explicit_ofw_unit_address)(const SysBusDevice *dev);
    void (*connect_irq_notifier)(SysBusDevice *dev, qemu_irq irq);
} SysBusDeviceClass;
  • SysBusDeviceClass首先屬於一個設備,所以父類爲DeviceClass
  • init用於初始化SysBusDevice
  • explicit_ofw_unit_address 用於返回ofw單元地址
  • connect_irq_notifier連接irq通知????
struct SysBusDevice {
    /*< private >*/
    DeviceState parent_obj;
    /*< public >*/

    int num_mmio;
    struct {
        hwaddr addr;
        MemoryRegion *memory;
    } mmio[QDEV_MAX_MMIO];
    int num_pio;
    uint32_t pio[QDEV_MAX_PIO];
};

具體的設備實例
num_mmio 也就是mmio區域的個數。
mmio 具體的mmio區域
關於io/memory模型請參考qemu 內存模型(1)—文檔和系列文章。

sysbus_device_type_info 對類的初始化函數爲sysbus_device_class_init,對實例的初始化函數由於是抽象類,沒有沒有做具體的實現。 所以我們來看sysbus_device_class_init函數

static void sysbus_device_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *k = DEVICE_CLASS(klass);
    k->init = sysbus_device_init;
    k->bus_type = TYPE_SYSTEM_BUS;
    /*
     * device_add plugs devices into a suitable bus.  For "real" buses,
     * that actually connects the device.  For sysbus, the connections
     * need to be made separately, and device_add can't do that.  The
     * device would be left unconnected, and will probably not work
     *
     * However, a few machines can handle device_add/-device with
     * a few specific sysbus devices. In those cases, the device
     * subclass needs to override it and set user_creatable=true.
     */
    k->user_creatable = false;
}

static int sysbus_device_init(DeviceState *dev)
{
    SysBusDevice *sd = SYS_BUS_DEVICE(dev);
    SysBusDeviceClass *sbc = SYS_BUS_DEVICE_GET_CLASS(sd);

    if (!sbc->init) {
        return 0;
    }
    return sbc->init(sd);
}

sysbus_init_ioports 函數用於設置pio端口

void sysbus_init_ioports(SysBusDevice *dev, uint32_t ioport, uint32_t size)
{
    uint32_t i;

    for (i = 0; i < size; i++) {
        assert(dev->num_pio < QDEV_MAX_PIO);
        dev->pio[dev->num_pio++] = ioport++;
    }
}

設置一段連續的io端口。x86裏面io端口有65535個,每個設備做多佔用32個端口, 將端口信息保存在pio數組裏面。個數信息保存在num_pio中。

sysbus_init_mmio函數用於設置設備的mmio

void sysbus_init_mmio(SysBusDevice *dev, MemoryRegion *memory)
{
    int n;

    assert(dev->num_mmio < QDEV_MAX_MMIO);
    n = dev->num_mmio++;
    dev->mmio[n].addr = -1;
    dev->mmio[n].memory = memory;
}

也是最多32段,addr爲hwaddr也就是gpa,暫時設置爲-1. 在sysbus_mmio_map 或者sysbus_mmio_map_overlap函數設置。

void sysbus_mmio_map_overlap(SysBusDevice *dev, int n, hwaddr addr,
                             int priority)
{
    sysbus_mmio_map_common(dev, n, addr, true, priority);
}

void sysbus_mmio_map(SysBusDevice *dev, int n, hwaddr addr)
{
    sysbus_mmio_map_common(dev, n, addr, false, 0);
}


static void sysbus_mmio_map_common(SysBusDevice *dev, int n, hwaddr addr,
                                   bool may_overlap, int priority)
{
    assert(n >= 0 && n < dev->num_mmio);

    if (dev->mmio[n].addr == addr) {
        /* ??? region already mapped here.  */
        return;
    }
    if (dev->mmio[n].addr != (hwaddr)-1) {
        /* Unregister previous mapping.  */
        memory_region_del_subregion(get_system_memory(), dev->mmio[n].memory);
    }
    dev->mmio[n].addr = addr;
    if (may_overlap) {
        memory_region_add_subregion_overlap(get_system_memory(),
                                            addr,
                                            dev->mmio[n].memory,
                                            priority);
    }
    else {
        memory_region_add_subregion(get_system_memory(),
                                    addr,
                                    dev->mmio[n].memory);
    }
}

sysbus_mmio_map 或者sysbus_mmio_map_overlap 會把SysBusDevice的第n個mmio的memoryregion添加到地址空間中。 sysbus_mmio_map_overlap 會調用memory_region_add_subregion_overlap來指定memoryregion的優先級,而sysbus_mmio_map 設置默認優先級爲0.

/* Request an IRQ source.  The actual IRQ object may be populated later.  */
void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p)
{
    qdev_init_gpio_out_named(DEVICE(dev), p, SYSBUS_DEVICE_GPIO_IRQ, 1);
}

void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq)
{
    SysBusDeviceClass *sbd = SYS_BUS_DEVICE_GET_CLASS(dev);

    qdev_connect_gpio_out_named(DEVICE(dev), SYSBUS_DEVICE_GPIO_IRQ, n, irq);

    if (sbd->connect_irq_notifier) {
        sbd->connect_irq_notifier(dev, irq);
    }
}

sysbus_init_irq 用於初始化中斷輸出引腳, 其實就是調用qdev_init_gpio_out_named設置一個link屬性. sysbus_connect_irq 函數則設置初始化輸出引腳, 其實是調用qdev_connect_gpio_out_named函數來設置的(就是設置qdev_init_gpio_out_named添加的link屬性).

再來總結下sysbus的用法
void sysbus_init_mmio(SysBusDevice *dev, MemoryRegion *memory); 初始化sysbus上的設備的mmio內存空間
MemoryRegion *sysbus_mmio_get_region(SysBusDevice *dev, int n) 獲取sysbus上設備的第n個內存空間
void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p) 初始化irq輸出引腳
void sysbus_pass_irq(SysBusDevice *dev, SysBusDevice *target);
void sysbus_init_ioports(SysBusDevice *dev, uint32_t ioport, uint32_t size) 初始化io端口
void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq) 設置irq輸出引腳
void sysbus_mmio_map(SysBusDevice *dev, int n, hwaddr addr) 設置mmio地址, n爲第n個MemoryRegion
void sysbus_mmio_map_overlap(SysBusDevice *dev, int n, hwaddr addr,
int priority) 設置mmio地址
void sysbus_add_io(SysBusDevice *dev, hwaddr addr,
MemoryRegion *mem); 給sysbus增加io port地址空間
MemoryRegion *sysbus_address_space(SysBusDevice *dev) 返回sysbus的地址空間
static inline DeviceState *sysbus_create_simple(const char *name,
hwaddr addr,
qemu_irq irq) 在sysbus上創建設備

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章