4. 中断处理测试
任务说明
本案例在QEMU中创建一个自定义PCI虚拟设备,该设备具备中断触发能力。客户机通过MMIO寄存器写入触发命令后,设备向中断控制器发送中断请求,客户机内核中的中断处理程序响应并记录中断次数。通过对比触发次数与客户机内记录的中断次数,验证虚拟设备中断处理流程的正确性。
虚拟设备实现
创建设备文件 interrupt-test-device.c,实现一个简单的PCI设备:
#include "qemu/osdep.h"
#include "hw/pci/pci_device.h"
#include "hw/pci/msi.h"
#include "qemu/log.h"
#define TYPE_INTERRUPT_TEST_DEVICE "interrupt-test-device"
#define INTERRUPT_TEST_DEVICE(obj) \
OBJECT_CHECK(InterruptTestDevice, (obj), TYPE_INTERRUPT_TEST_DEVICE)
#define INT_TEST_MMIO_BAR 0
#define INT_TEST_MMIO_SIZE 0x1000
#define REG_TRIGGER 0x00
#define REG_STATUS 0x04
#define REG_IRQ_COUNT 0x08
typedef struct {
PCIDevice parent_obj;
MemoryRegion mmio;
uint32_t irq_count;
uint32_t status;
} InterruptTestDevice;
static void int_test_update_irq(InterruptTestDevice *dev)
{
/* 当status的第0位为1时,触发中断 */
int level = (dev->status & 0x1) ? 1 : 0;
pci_set_irq(&dev->parent_obj, level);
}
static uint64_t int_test_mmio_read(void *opaque, hwaddr addr, unsigned size)
{
InterruptTestDevice *dev = opaque;
uint32_t val = 0;
switch (addr) {
case REG_STATUS:
val = dev->status;
break;
case REG_IRQ_COUNT:
val = dev->irq_count;
break;
default:
val = 0;
break;
}
return val;
}
static void int_test_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
InterruptTestDevice *dev = opaque;
switch (addr) {
case REG_TRIGGER:
/* 写入任意值触发一次中断 */
dev->irq_count++;
dev->status |= 0x1;
int_test_update_irq(dev);
break;
case REG_STATUS:
/* 写入1清除中断状态 */
if (val & 0x1) {
dev->status &= ~0x1;
int_test_update_irq(dev);
}
break;
default:
break;
}
}
static const MemoryRegionOps int_test_mmio_ops = {
.read = int_test_mmio_read,
.write = int_test_mmio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void int_test_realize(PCIDevice *pci_dev, Error **errp)
{
InterruptTestDevice *dev = INTERRUPT_TEST_DEVICE(pci_dev);
memory_region_init_io(&dev->mmio, OBJECT(dev), &int_test_mmio_ops,
dev, "int-test-mmio", INT_TEST_MMIO_SIZE);
pci_register_bar(pci_dev, INT_TEST_MMIO_BAR,
PCI_BASE_ADDRESS_SPACE_MEMORY, &dev->mmio);
}
static void int_test_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
k->realize = int_test_realize;
k->vendor_id = 0x1234;
k->device_id = 0x5678;
k->class_id = PCI_CLASS_OTHERS;
dc->desc = "Interrupt Test Device";
}
static const TypeInfo int_test_info = {
.name = TYPE_INTERRUPT_TEST_DEVICE,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(InterruptTestDevice),
.class_init = int_test_class_init,
.interfaces = (InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ }
},
};
static void int_test_register_types(void)
{
type_register_static(&int_test_info);
}
type_init(int_test_register_types)
客户机驱动与测试程序
在客户机内编写驱动 int_test_drv.c,注册中断处理程序并暴露中断次数:
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#define INT_TEST_VENDOR 0x1234
#define INT_TEST_DEVICE 0x5678
#define REG_TRIGGER 0x00
#define REG_STATUS 0x04
#define REG_IRQ_COUNT 0x08
static void __iomem *mmio_base;
static int irq_line;
static atomic_t irq_received = ATOMIC_INIT(0);
static irqreturn_t int_test_isr(int irq, void *dev_id)
{
atomic_inc(&irq_received);
/* 清除设备中断状态 */
iowrite32(0x1, mmio_base + REG_STATUS);
return IRQ_HANDLED;
}
static int int_test_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int ret;
ret = pci_enable_device(pdev);
if (ret)
return ret;
ret = pci_request_regions(pdev, "int_test");
if (ret)
goto err_disable;
mmio_base = pci_iomap(pdev, 0, 0x1000);
if (!mmio_base) {
ret = -ENOMEM;
goto err_regions;
}
ret = request_irq(pdev->irq, int_test_isr, IRQF_SHARED,
"int_test", &irq_line);
if (ret)
goto err_unmap;
irq_line = pdev->irq;
pci_set_drvdata(pdev, &irq_line);
return 0;
err_unmap:
pci_iounmap(pdev, mmio_base);
err_regions:
pci_release_regions(pdev);
err_disable:
pci_disable_device(pdev);
return ret;
}
static void int_test_remove(struct pci_dev *pdev)
{
free_irq(pdev->irq, &irq_line);
pci_iounmap(pdev, mmio_base);
pci_release_regions(pdev);
pci_disable_device(pdev);
}
static const struct pci_device_id int_test_ids[] = {
{ PCI_DEVICE(INT_TEST_VENDOR, INT_TEST_DEVICE) },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, int_test_ids);
static struct pci_driver int_test_driver = {
.name = "int_test",
.id_table = int_test_ids,
.probe = int_test_probe,
.remove = int_test_remove,
};
module_pci_driver(int_test_driver);
/* 导出中断次数读取接口 */
static int __init int_test_init(void)
{
return 0;
}
static void __exit int_test_exit(void)
{
}
module_init(int_test_init);
module_exit(int_test_exit);
MODULE_LICENSE("GPL");
客户机内测试程序 test_int.c:
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define PCI_DEV_PATH "/sys/bus/pci/devices/0000:00:0b.0/resource0"
#define REG_TRIGGER 0x00
#define REG_STATUS 0x04
#define REG_IRQ_COUNT 0x08
int main(void)
{
int fd = open(PCI_DEV_PATH, O_RDWR | O_SYNC);
if (fd < 0) {
perror("open");
return 1;
}
void *mmio = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (mmio == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
/* 读取设备侧触发计数 */
uint32_t dev_count = *(volatile uint32_t *)((char *)mmio + REG_IRQ_COUNT);
printf("Device IRQ count before: %u\n", dev_count);
/* 触发3次中断 */
for (int i = 0; i < 3; i++) {
*(volatile uint32_t *)((char *)mmio + REG_TRIGGER) = 1;
usleep(100000); /* 等待中断处理完成 */
}
/* 读取设备侧计数 */
dev_count = *(volatile uint32_t *)((char *)mmio + REG_IRQ_COUNT);
printf("Device IRQ count after: %u\n", dev_count);
/* 读取驱动记录的中断次数 */
FILE *fp = fopen("/proc/interrupts", "r");
if (!fp) {
perror("fopen");
munmap(mmio, 0x1000);
close(fd);
return 1;
}
char line[256];
int found = 0;
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, "int_test")) {
printf("Driver IRQ info: %s", line);
found = 1;
break;
}
}
fclose(fp);
if (!found)
printf("int_test not found in /proc/interrupts\n");
munmap(mmio, 0x1000);
close(fd);
return 0;
}
编译与运行
QEMU侧编译设备:
# 在QEMU源码目录下
./configure --target-list=x86_64-softmmu
make
# 将interrupt-test-device.c放入hw/misc/并添加到对应Makefile.objs
启动QEMU并挂载设备:
qemu-system-x86_64 \
-kernel /path/to/bzImage \
-initrd /path/to/initrd.img \
-append "root=/dev/ram0 console=ttyS0" \
-device interrupt-test-device \
-nographic
客户机内编译驱动与测试:
# 客户机内
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
insmod int_test_drv.ko
gcc -o test_int test_int.c
./test_int
验证方法与判定标准
执行测试程序后,观察以下输出并判定:
- 设备侧
REG_IRQ_COUNT在触发后应为3(与触发次数一致); /proc/interrupts中int_test对应的中断计数应增加3次;- 若设备侧计数为3且驱动中断计数为3,则中断触发、传递、处理、状态清除全流程正确,测试通过。
预期输出示例
Device IRQ count before: 0
Device IRQ count after: 3
Driver IRQ info: 0: 3 IO-APIC 8-edge int_test
以上输出中设备侧计数为3,驱动中断计数为3,两者一致即判定测试通过。