QEMU ARM interrupt system architecture
作者:互联网
QEMU interrupt system use GPIO to implement interrupt system. We can understand it by a simple model:
Device.[GPIO_OUT] ->[GPIO_IN].GIC.[GPIO_OUT]->[GPIO_IN].core
GPIO_IN IRQ is created by qdev_init_gpio_in()
GPIO_OUT IRQ is initialized by sysbus_init_irq()->qdev_init_gpio_out_named(). Attention, GPIO_OUT IRQ is not created. Actually it is just a IRQ pointer, and the IRQ pointer will point to the GPIO_IN IRQ which connected. sysbus_connect_irq() connects GPIO_OUT and GPIO_IN IRQ.
Here let's take vexpress-a9 as the example.
ARM core:
ARM core has irq,fiq,virq,vfiq for GPIO_IN IRQ. they are created at:
arm_cpu_initfn()->qdev_init_gpio_in(DEVICE(cpu), arm_cpu_set_irq, 4);
GIC:
GIC has 96 GPIO_IN IRQs and 4 GPIO_OUT IRQs, and they are created at:
arm_gic_realize()->gic_init_irqs_and_mmio()
void gic_init_irqs_and_mmio(GICState *s, qemu_irq_handler handler,
const MemoryRegionOps *ops,
const MemoryRegionOps *virt_ops)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(s);
int i = s->num_irq - GIC_INTERNAL;
/* For the GIC, also expose incoming GPIO lines for PPIs for each CPU.
* GPIO array layout is thus:
* [0..N-1] SPIs
* [N..N+31] PPIs for CPU 0
* [N+32..N+63] PPIs for CPU 1
* ...
*/
i += (GIC_INTERNAL * s->num_cpu);
qdev_init_gpio_in(DEVICE(s), handler, i); // GPIO_IN IRQs
// GPIO_OUT IRQs
for (i = 0; i < s->num_cpu; i++) {
sysbus_init_irq(sbd, &s->parent_irq[i]);
}
for (i = 0; i < s->num_cpu; i++) {
sysbus_init_irq(sbd, &s->parent_fiq[i]);
}
for (i = 0; i < s->num_cpu; i++) {
sysbus_init_irq(sbd, &s->parent_virq[i]);
}
for (i = 0; i < s->num_cpu; i++) {
sysbus_init_irq(sbd, &s->parent_vfiq[i]);
}
....
}
GICState is GIC state logic. We can see it has parent_irq[],parent_fiq[],parent_virq[],parent_vfiq[] 4 GPIO_OUT IRQs.
Actually they are all IRQ pointers. They will point to core's 4 GPIO_IN IRQs. Where do them connect? in init_cpus().
static void init_cpus(MachineState *ms, const char *cpu_type,
const char *privdev, hwaddr periphbase,
qemu_irq *pic, bool secure, bool virt)
{
DeviceState *dev;
SysBusDevice *busdev;
.....
.....
dev = qdev_create(NULL, privdev);
busdev = SYS_BUS_DEVICE(dev); /*here busdev is a9mpcore_priv*/
.....
for (n = 0; n < smp_cpus; n++) {
DeviceState *cpudev = DEVICE(qemu_get_cpu(n));
sysbus_connect_irq(busdev, n, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
sysbus_connect_irq(busdev, n + smp_cpus,
qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
sysbus_connect_irq(busdev, n + 2 * smp_cpus,
qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ));
sysbus_connect_irq(busdev, n + 3 * smp_cpus,
qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ));
}
.....
}
sysbus_connect_irq() connect device's GPIO_OUT IRQ to CPU's GPIO_IN IRQ. But here device is a9mpcore_priv, not GIT.
How does it work?
In a9mp_priv_realize(), there is a IRQ pass code:
sysbus_pass_irq(sbd, gicbusdev);
Here sbd presents a9mpcore_priv, gicbusdev presents GIC. This function make them connect, and a9mpcore_priv can use GIC's GPIO_OUT IRQ.
Device:
let's take UART as example.
Device need initialize GPIO_OUT IRQ and connect to GIC GPIO_IN IRQ.
Initialzation at:
static void pl011_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
PL011State *s = PL011(obj);
int i;
.....
for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
sysbus_init_irq(sbd, &s->irq[i]);
printf("alexxie:%s, uart irq at %p\n",__func__,s->irq[i]);
}
.....
}
Connection at:
static void vexpress_common_init(MachineState *machine)
{
qemu_irq pic[64];
pl011_create(map[VE_UART0], pic[5], serial_hd(0));
pl011_create(map[VE_UART1], pic[6], serial_hd(1));
pl011_create(map[VE_UART2], pic[7], serial_hd(2));
pl011_create(map[VE_UART3], pic[8], serial_hd(3));
}
map[] is address map of the vexpress. pic[] is the GIC GPIO_IN IRQ.
Let's read pl011_create firstly, then back to pic[].
static inline DeviceState *pl011_create(hwaddr addr,
qemu_irq irq,
Chardev *chr)
{
DeviceState *dev;
SysBusDevice *s;
PL011State *uart_s ;
dev = qdev_create(NULL, "pl011");
s = SYS_BUS_DEVICE(dev);
.....
sysbus_connect_irq(s, 0, irq); //connect irq
uart_s = PL011(dev);
printf("alexdebug:%s, uart irq is at %p,%p,%p,%p,%p,%p\n",__func__,uart_s->irq[0],uart_s->irq[1],uart_s->irq[2],uart_s->irq[3],uart_s->irq[4],uart_s->irq[5]);
return dev;
}
Here we can see sysbus_connect_irq() is called to connect pl011 to irq. irq is from pic[5],pic[6],pic[7] and pic[8].
How do we know pic[] is from GIC GPIO_IN? let's check the code.
static void vexpress_common_init(MachineState *machine)
{
VexpressMachineClass *vmc = VEXPRESS_MACHINE_GET_CLASS(machine);
VEDBoardInfo *daughterboard = vmc->daughterboard;
qemu_irq pic[64];
.....
daughterboard->init(vms, machine->ram_size, machine->cpu_type, pic);
.....
}
daughterboard->init is a9_daughterboard_init()
static void a9_daughterboard_init(const VexpressMachineState *vms,
ram_addr_t ram_size,
const char *cpu_type,
qemu_irq *pic)
{
/* 0x1e000000 A9MPCore (SCU) private memory region */
init_cpus(machine, cpu_type, TYPE_A9MPCORE_PRIV, 0x1e000000, pic,
vms->secure, vms->virt);
}
keep dive into init_cpus()
static void init_cpus(MachineState *ms, const char *cpu_type,
const char *privdev, hwaddr periphbase,
qemu_irq *pic, bool secure, bool virt)
{
DeviceState *dev;
.....
dev = qdev_create(NULL, privdev);
.....
{
a9_s = A9MPCORE_PRIV(dev);
printf("alexdebug:%s, GIC parent_irq at %p\n",__func__,a9_s->gic.parent_irq[0]);
}
for (n = 0; n < 64; n++) {
DeviceState* gicdev=DEVICE(&a9_s->gic);
pic[n] = qdev_get_gpio_in(gicdev, n); //get gic gpio_in
//pic[n] = qdev_get_gpio_in(dev, n);
printf("alexxie:%s, pic[%d] at %p\n",__func__,n,pic[n]);
}
.....
}
OK, here we can see pic[] is from GIC by function qdev_get_gpio_in(). You may noticed the code is changed by me. Yes, the origin code pic[] is from a9mpcore_priv and a9mpcore_priv will call GIC. I modified it because I want to try if I can make it simpler to remove one layer of GPIO. it works. So the code is current look.
OK, to here, the question is answered.
标签:irq,pic,init,GIC,architecture,IRQ,interrupt,GPIO,QEMU 来源: https://blog.csdn.net/alex_mianmian/article/details/98174812