驱动开发系列54 - Linux Graphics QXL显卡驱动代码分析(一)设备初始化

一:概述

QXL 是QEMU支持的一种虚拟显卡,用于虚拟化环境中的图形加速,旨在提高虚拟机的图形显示和远程桌面的用户体验;QEMU 也称 Quick Emulator,快速仿真器,是一个开源通用的仿真和虚拟化工具,可以模拟不同架构的计算机以及运行虚拟机。本文介绍下QXL 虚拟显卡驱动,即Linux 内核代码中QXL显卡驱动部分。看看它是如何支持QEMU虚拟显卡的。 QXL代码分析基于v6.15-rc2内核版本。

二:模块初始化

QXL 虚拟显卡是一个PCI设备,它需要QXL显卡驱动支持显示模式 (modeset) 设置能力,即支持在内核设置输出参数的能力(分辨率,帧率)。比如将显示器设置成1920x1080分辨率。同时QXL显卡驱动在Linux 内核代码中,它是一个独立的内核模块,代码位置是linux/drivers/gpu/drm/qxl。所以QXL内核模块的初始化方式是:

cpp 复制代码
drm_module_pci_driver_if_modeset(qxl_pci_driver, qxl_modeset);

这行代码的意思是:QXL 模块告诉内核,我是一个 PCI 设备驱动模块(基于 DRM 框架),如果你支持 modeset,那我会自动注册 qxl_pci_driver。

三:PCI设备初始化

QXL显卡是PCI设备,所以前面模块初始化时注册了qxl_pci_driver, 下面看看 qxl_pci_driver的定义;

cpp 复制代码
static struct pci_driver qxl_pci_driver = {
	 .name = DRIVER_NAME,
	 .id_table = pciidlist,
	 .probe = qxl_pci_probe,
	 .remove = qxl_pci_remove,
	 .shutdown = qxl_pci_shutdown,
	 .driver.pm = &qxl_pm_ops,
};

这段代码的意思是:我这个 QXL 驱动支持这些 PCI ID,如果你发现这样的设备,就用qxl_pci_probe() 来初始化,拔出时用 qxl_pci_remove() 清理,关机时调用 qxl_pci_shutdown,需要挂起/恢复就用 qxl_pm_ops 处理。

下面这个 pciidlist 是 QXL 驱动和 PCI 总线的匹配表:

cpp 复制代码
static const struct pci_device_id pciidlist[] = {
    { 0x1b36, 0x100, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8, 0xffff00, 0 },
    { 0x1b36, 0x100, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_OTHER << 8, 0xffff00, 0 },
    { 0, 0, 0 },
};

这里面每个结构是一个 struct pci_device_id,用于匹配设备的 Vendor ID、Device ID、Class 等信息:

cpp 复制代码
{
    .vendor     = 0x1b36,     // 厂商 ID(Red Hat QEMU 的 PCI 厂商 ID)
    .device     = 0x0100,     // 设备 ID(QXL 虚拟显卡的 ID)
    .subvendor  = PCI_ANY_ID, // 子系统厂商 ID(任意)
    .subdevice  = PCI_ANY_ID, // 子系统设备 ID(任意)
    .class      = PCI_CLASS_DISPLAY_VGA << 8, // 设备类(VGA 显卡类)
    .class_mask = 0xffff00,   // 匹配 class 的掩码
    .driver_data = 0          // 可用于传递自定义参数,这里设为 0
}

当内核检测到有匹配的PCI设备插入时,即检测到QXL显卡设备时,就调用设备初始化函数qxl_pci_probe,下面我们看看它做了什么工作:

cpp 复制代码
static int
qxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	struct qxl_device *qdev;
	int ret;

	if (pdev->revision < 4) {
		DRM_ERROR("qxl too old, doesn't support client_monitors_config,"
			  " use xf86-video-qxl in user mode");
		return -EINVAL; /* TODO: ENODEV ? */
	}

	qdev = devm_drm_dev_alloc(&pdev->dev, &qxl_driver,
				  struct qxl_device, ddev);
	if (IS_ERR(qdev)) {
		pr_err("Unable to init drm dev");
		return -ENOMEM;
	}

	ret = pci_enable_device(pdev);
	if (ret)
		return ret;

	ret = aperture_remove_conflicting_pci_devices(pdev, qxl_driver.name);
	if (ret)
		goto disable_pci;

	if (pci_is_vga(pdev) && pdev->revision < 5) {
		ret = vga_get_interruptible(pdev, VGA_RSRC_LEGACY_IO);
		if (ret) {
			DRM_ERROR("can't get legacy vga ioports\n");
			goto disable_pci;
		}
	}

	ret = qxl_device_init(qdev, pdev);
	if (ret)
		goto put_vga;

	ret = qxl_modeset_init(qdev);
	if (ret)
		goto unload;

	drm_kms_helper_poll_init(&qdev->ddev);

	/* Complete initialization. */
	ret = drm_dev_register(&qdev->ddev, ent->driver_data);
	if (ret)
		goto modeset_cleanup;

	drm_client_setup(&qdev->ddev, NULL);
	return 0;

modeset_cleanup:
	qxl_modeset_fini(qdev);
unload:
	qxl_device_fini(qdev);
put_vga:
	if (pci_is_vga(pdev) && pdev->revision < 5)
		vga_put(pdev, VGA_RSRC_LEGACY_IO);
disable_pci:
	pci_disable_device(pdev);

	return ret;
}

这个代码做的第一件事就是分配一个qxl_device设备;并注册了一个 qxl_driver 驱动,与qxl_device建立绑定关系;关于qxl_device, qxl_driver 后面再说;这里可以理解为他们是QXL设备,和对应的QXL设备驱动。

这个代码做的第二件事就是打开PCI BAR和使能PCI中断访问。也就是说PCI BAR 是PCI设备暴露的"资源入口",通过它内核可以访问显卡的内存,I/O端口和使能中断,实现真正驱动硬件的能力;PCI设备可以产生中断,比如GPU渲染完成时通知CPU。

这个代码做的第三件事就是初始化QXL设备的显存,寄存器等,这个后面再详细展开说。

这个代码做的第四件事就是配置QXL设备的显示输出(KMS),比如CRTC,Encoder,Connector,这个后面再详细展开说。

这个代码做的第五件事就是向内核注册QXL设备,这样用户空间才能通过 /dev/dri/cardX 访问到该设备。

四:QXL设备初始化

前面在 qxl_pci_probe 分配和初始化了 qxl_device,并将其和qxl_drvier建立了绑定关系。下面看看qxl_driver 和 qxl_device 的定义。

cpp 复制代码
static struct drm_driver qxl_driver = {
	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_CURSOR_HOTSPOT,

	.dumb_create = qxl_mode_dumb_create,
	.dumb_map_offset = drm_gem_ttm_dumb_map_offset,
#if defined(CONFIG_DEBUG_FS)
	.debugfs_init = qxl_debugfs_init,
#endif
	.gem_prime_import_sg_table = qxl_gem_prime_import_sg_table,
	DRM_FBDEV_TTM_DRIVER_OPS,
	.fops = &qxl_fops,
	.ioctls = qxl_ioctls,
	.num_ioctls = ARRAY_SIZE(qxl_ioctls),
	.name = DRIVER_NAME,
	.desc = DRIVER_DESC,
	.major = 0,
	.minor = 1,
	.patchlevel = 0,

	.release = qxl_drm_release,
};

这个代码定义了一个 drm_driver 结构体,从这里也可以看出,QXL驱动确实是基于DRM框架的(DRM介绍详见本专栏的相关文章)。 drm_device 是QXL显卡在Linux DRM 框架中的核心驱动结构,这个结构注册后会与DRM核心框架配合,为显卡提供设备初始化,缓冲区管理,图形输出等功能。

cpp 复制代码
struct qxl_device {
	struct drm_device ddev;

	resource_size_t vram_base, vram_size;
	resource_size_t surfaceram_base, surfaceram_size;
	resource_size_t rom_base, rom_size;
	struct qxl_rom *rom;

	struct qxl_mode *modes;
	struct qxl_bo *monitors_config_bo;
	struct qxl_monitors_config *monitors_config;

	/* last received client_monitors_config */
	struct qxl_monitors_config *client_monitors_config;

	int io_base;
	void *ram;
	struct qxl_mman		mman;
	struct qxl_gem		gem;

	void *ram_physical;

	struct qxl_ring *release_ring;
	struct qxl_ring *command_ring;
	struct qxl_ring *cursor_ring;

	struct qxl_ram_header *ram_header;

	struct qxl_bo *primary_bo;
	struct qxl_bo *dumb_shadow_bo;
	struct qxl_head *dumb_heads;

	struct qxl_memslot main_slot;
	struct qxl_memslot surfaces_slot;

	spinlock_t	release_lock;
	struct idr	release_idr;
	uint32_t	release_seqno;
	atomic_t	release_count;
	wait_queue_head_t release_event;
	spinlock_t release_idr_lock;
	struct mutex	async_io_mutex;
	unsigned int last_sent_io_cmd;

	/* interrupt handling */
	atomic_t irq_received;
	atomic_t irq_received_display;
	atomic_t irq_received_cursor;
	atomic_t irq_received_io_cmd;
	unsigned int irq_received_error;
	wait_queue_head_t display_event;
	wait_queue_head_t cursor_event;
	wait_queue_head_t io_cmd_event;
	struct work_struct client_monitors_config_work;

	/* debugfs */
	struct qxl_debugfs	debugfs[QXL_DEBUGFS_MAX_COMPONENTS];
	unsigned int debugfs_count;

	struct mutex		update_area_mutex;

	struct idr	surf_id_idr;
	spinlock_t surf_id_idr_lock;
	int last_alloced_surf_id;

	struct mutex surf_evict_mutex;
	struct io_mapping *vram_mapping;
	struct io_mapping *surface_mapping;

	/* */
	struct mutex release_mutex;
	struct qxl_bo *current_release_bo[3];
	int current_release_bo_offset[3];

	struct work_struct gc_work;

	struct drm_property *hotplug_mode_update_property;
	int monitors_config_width;
	int monitors_config_height;
};

qxl_device 是QXL DRM驱动中定义的"设备结构体",代表一个QXL显卡实例,在驱动运行时用于管理显卡的状态、资源、内存和与内核/用户空间交互。它是QXL驱动的核心数据结构,每个被探测到的QXL设备(比如前面说的通过PCI探测)都会分配这样一个结构并初始化,贯穿设备的整个生命周期。

下面看看QXL设备驱动是如何操作QXL设备的。

相关推荐
53Hz4 分钟前
【问题记录】kmemleak 定位内存泄露
linux
53Hz4 分钟前
【调试工具】taskset 设置处理器的亲和度
linux
真成运维5 分钟前
生产环境变更 AppSpace存储切换
linux
53Hz10 分钟前
【调试工具】coredump 使用示例
linux
53Hz10 分钟前
【问题记录】进程调度导致 UDP 丢包问题分析
linux
53Hz12 分钟前
【调试工具】pstore 工具配置及使用
linux
53Hz13 分钟前
【问题记录】如何打包两个dtb文件
linux
hweiyu0028 分钟前
Linux 命令:less
linux·数据库
cui_win31 分钟前
【网络】Linux 内核优化实战 - net.netfilter.nf_conntrack_buckets
linux·网络·.net
cui_win32 分钟前
【网络】Linux 内核优化实战 - net.netfilter.nf_conntrack_tcp_timeout_established
linux·网络·.net