显示设备驱动开发-LMLPHP
显示设备驱动开发-LMLPHP


下面我们以Linux虚拟环境中常用的qxl显卡为例来看添加一个新的显卡驱动,都要完成哪些内容。

一、注册驱动

目前除去一些片内的显示芯片,常见显卡板卡都是pci接口,所以这里注册一个pci驱动。

static int __init qxl_init(void)

{

...

        qxl_driver.num_ioctls = qxl_max_ioctls;

        return pci_register_driver(&qxl_pci_driver);

}

pci驱动的结构中,probe函数是pci设备的初始化过程,这个pci设备作为一个显卡设备会被初始化化。

static struct pci_driver qxl_pci_driver = {

         .name = DRIVER_NAME,

         .id_table = pciidlist,

         .probe = qxl_pci_probe,

         .remove = qxl_pci_remove,

         .driver.pm = qxl_pm_ops,

};

二、设备初始化

在pci设备初始化过程中,将设备初始化为DRM框架下的一个显示驱动设备。如下代码中,这列举出了qxl_pci_probe核心功能函数,完整源码参考Uos源码仓库源码包或Uos社区内核源码。

static int

qxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)

{

...

ret = pci_enable_device(pdev);

...

ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, 0, "qxl");

...

ret = qxl_device_init(qdev, &qxl_driver, pdev);

...

ret = qxl_modeset_init(qdev);

...

ret = drm_dev_register(&qdev->ddev, ent->driver_data);

...

}

pci_enable_device()函数初始化一个pci设备,这是在被使用前的必要步骤,通常是设置底层使能I/O和内存。

drm_fb_helper_remove_conflicting_pci_framebuffers()函数清楚之前使用的framebuffer设置,从这里开始qxl驱动负责显示framebuffer的管理。 这通常是在启动过程中framebuffer的管理权限专一的时候使用。

qxl_device_init()、qxl_modeset_init()是实际做qxl初始化的部分,这部分要在注册drm驱动之前完成。这部分是显卡专有的参数、功能的准备,保证注册为drm设备后就可以正常工作。qxl_driver是qxl提供的qxl这个DRM设备对应的DRM驱动。这里需要关注一下drm_driver结构体里的load函数,通常是显卡驱动加载kms的入口。

static struct drm_driver qxl_driver = {

        .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |

                           DRIVER_ATOMIC | DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED,

        .dumb_create = qxl_mode_dumb_create,

        .dumb_map_offset = qxl_mode_dumb_mmap,

#if defined(CONFIG_DEBUG_FS)

        .debugfs_init = qxl_debugfs_init,

#endif

        .prime_handle_to_fd = drm_gem_prime_handle_to_fd,

        .prime_fd_to_handle = drm_gem_prime_fd_to_handle,

        .gem_prime_export = drm_gem_prime_export,

        .gem_prime_import = drm_gem_prime_import,

        .gem_prime_pin = qxl_gem_prime_pin,

        .gem_prime_unpin = qxl_gem_prime_unpin,

        .gem_prime_get_sg_table = qxl_gem_prime_get_sg_table,

        .gem_prime_import_sg_table = qxl_gem_prime_import_sg_table,

        .gem_prime_vmap = qxl_gem_prime_vmap,

        .gem_prime_vunmap = qxl_gem_prime_vunmap,

        .gem_prime_mmap = qxl_gem_prime_mmap,

        .gem_free_object_unlocked = qxl_gem_object_free,

        .gem_open_object = qxl_gem_object_open,

        .gem_close_object = qxl_gem_object_close,

        .fops = qxl_fops,

        .ioctls = qxl_ioctls,

        .irq_handler = qxl_irq_handler,

        .name = DRIVER_NAME,

        .desc = DRIVER_DESC,

        .date = DRIVER_DATE,

        .major = 0,

        .minor = 1,

        .patchlevel = 0,

};

qxl_fops是用户层操作设备节点/dev/dri/cardX的时候,对应的操作方法。显卡驱动往往是在这里封装drm的函数,比如: 自定义qxl_open()函数,做专业的操作,然后调用drm_open()。

static const struct file_operations qxl_fops = {

        .owner = THIS_MODULE,

        .open = drm_open,

        .release = drm_release,

        .unlocked_ioctl = drm_ioctl,

        .poll = drm_poll,

        .read = drm_read,

        .mmap = qxl_mmap,

};

drm_dev_register()函数将qxl注册到DRM框架,成为一个DRM设备

三、注册DRM设备

这是显卡驱动将自己添加到DRM框架的入口,注册成功之后,用户层就可以正常使用这个显示设备做渲染、显示了。


int drm_dev_register(struct drm_device *dev, unsigned long flags)

{

...

ret = drm_minor_register(dev, DRM_MINOR_RENDER);

...

ret = drm_minor_register(dev, DRM_MINOR_PRIMARY);

...

if (dev->driver->load) {

        ret = dev->driver->load(dev, flags);

...

}

...

if (drm_core_check_feature(dev, DRIVER_MODESET))

        drm_modeset_register_all(dev);

...

}

DRM_MINOR_RENDER设备对应的是/dev/dri/renderX, DRM_MINOR_PRIMARY设备对应的是/dev/dri/cardX。

如果drm驱动提供了load方法,调用load函数完成kms初始化。

drm_modeset_register_all()函数完成DRM框架下的crtc、plane、encoder、connecter的初始化

int drm_modeset_register_all(struct drm_device *dev)

{               

        int ret;

        

        ret = drm_plane_register_all(dev);

        if (ret)

                goto err_plane;

                

        ret = drm_crtc_register_all(dev);

        if  (ret)

                goto err_crtc;

        

        ret = drm_encoder_register_all(dev);

        if (ret)

                goto err_encoder;

        ret = drm_connector_register_all(dev);

        if (ret) 

                goto err_connector;

        return 0;

...

}

通过上述三个步骤,显卡驱动就完成了自身初始化、并将自己添加到DRM显示框架当中。

四、显卡驱动集成方案

桌面版Linux图形显示系统的层次如下图所示,这是一个基于DRM框架的完整架构,UOS系统的显示功能也遵循这个框架层次的。

显示设备驱动开发-LMLPHP

Linux图形显示系统层次结构

UOS支持以完全开源、部分开源已经完全闭源的方式提供的显卡方案进行集成。当前UOS设备驱动的集成的技术架构下,推荐显卡厂商以Vendor的方式提供UMD。KMD可以按照实际情况是否开源来选择是集成进内核主线源码做核内驱动,还是以dkms的方式做核外驱动。

上图蓝色框内是厂商方案涉及的部分,通常显卡驱动方案包括:

  • 显示服务的DDX,通常命名方式是xxx_drv.so,存放目/usr/lib/xorg/modules/drivers/

  • 3D渲染的支持库,通常命名方式是xxx_dri.so, 推荐存放目录/usr/lib/-linux-gpu//

  • 特有的DRM API接口库,通常命名方式是libdrm_xxx.so, 推荐存放目录/usr/lib/-linux-gpu/

  • 显卡必要的固件firmware文件已经专有配置

  • 厂商提供的vaapi、vdpau视频解码库

  • 显卡内核驱动模块

显示设备驱动开发-LMLPHP

UOS显卡集成推荐方案层次图

UOS系统对厂商的方案支持的相关协议版本的要求如下表:


显示设备驱动开发-LMLPHP

12-09 20:37