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上創建設備