QT显示插件(LinuxFB)及其依赖的驱动(DRM/framebuffer)记录

发布时间 2023-05-27 21:20:53作者: ArnoldLu

关键词:Framebuffer、linuxfb、DRM等等。

 

QT在Linux中支持多种显示插件,包括EGLFS、LinuxFB、DirectFB、Wayland等。可以通过--platfrom选项指定选择何种插件。比如:./analogclock --platform linuxfb。

QT支持多种显示插件,显示插件打开Linux内核fb设备,Linux内核中GPU/Display驱动将应用数据刷新到Display设备上。此处,简单记录以上过程涉及到的模块。

1. QT5中linuxfb显示插件

class QPlatformScreen作为基类派生了显示接口类:

src\plugins\platforms\android\qandroidplatformscreen.h:
class QAndroidPlatformScreen: public QObject, public QPlatformScreen, public AndroidSurfaceClient

src\plugins\platforms\cocoa\qcocoascreen.h:
class QCocoaScreen : public QPlatformScreen

src\plugins\platforms\directfb\qdirectfbscreen.h:
class QDirectFbScreen : public QPlatformScreen

src\plugins\platforms\eglfs\api\qeglfsscreen_p.h:
class Q_EGLFS_EXPORT QEglFSScreen : public QPlatformScreen

src\platformsupport\fbconvenience:
class QFbScreen : public QObject, public QPlatformScreen

src\plugins\platforms\haiku\qhaikuscreen.h:
class QHaikuScreen : public QPlatformScreen

src\plugins\platforms\ios\qiosscreen.h:
class QIOSScreen : public QObject, public QPlatformScreen

src\plugins\platforms\minimalegl\qminimaleglscreen.h:
 class QMinimalEglScreen : public QPlatformScreen

src\plugins\platforms\minimal\qminimalintegration.h:
 class QMinimalScreen : public QPlatformScreen

src\plugins\platforms\offscreen\qoffscreencommon.h:
 class QOffscreenScreen : public QPlatformScreen

src\plugins\platforms\openwfd\qopenwfdscreen.h:
 class QOpenWFDScreen : public QPlatformScreen

src\plugins\platforms\qnx\qqnxscreen.h:
 class QQnxScreen : public QObject, public QPlatformScreen

src\plugins\platforms\wasm\qwasmscreen.h:
 class QWasmScreen : public QObject, public QPlatformScreen

src\plugins\platforms\windows\qwindowsscreen.h:
 class QWindowsScreen : public QPlatformScreen

src\plugins\platforms\winrt\qwinrtscreen.h:
 class QWinRTScreen : public QPlatformScreen

src\plugins\platforms\xcb\qxcbscreen.h:
 class Q_XCB_EXPORT QXcbScreen : public QXcbObject, public QPlatformScreen

其中QFbScreen有派生了如下显示类:

src\plugins\platforms\bsdfb\qbsdfbscreen.h:
 class QBsdFbScreen : public QFbScreen

src\plugins\platforms\integrity\qintegrityfbscreen.h:
 class QIntegrityFbScreen : public QFbScreen

src\plugins\platforms\linuxfb\qlinuxfbdrmscreen.h:
 class QLinuxFbDrmScreen : public QFbScreen

src\plugins\platforms\linuxfb\qlinuxfbscreen.h:
 class QLinuxFbScreen : public QFbScreen

src\plugins\platforms\vnc\qvncscreen.h:
 class QVncScreen : public QFbScreen

通过make menuconfig进入Target packages->Graphic libraries and applications(graphic/text)->QT5配置显示插件,以及默认插件:

 编译完成后在Target Rootfs中每个插件以库的形式保存: 

/usr/lib/qt/plugins/platforms/
|-- libqeglfs.so
|-- libqlinuxfb.so
|-- libqminimal.so
|-- libqminimalegl.so
|-- libqoffscreen.so
`-- libqvnc.so

2. QLinuxFbScreen插件

class QLinuxFbScreen : public QFbScreen
{
    Q_OBJECT
public:
    QLinuxFbScreen(const QStringList &args);--参数的初始化赋值。
    ~QLinuxFbScreen();--去初始化,内存去映射以及关闭fb句柄。

    bool initialize() override;--打开fb,获取显示相关参数,映射frame buffer到用户空间,并以frame buffer映射内存为缓存穿件QImage,最后清空屏幕。

    QPixmap grabWindow(WId wid, int x, int y, int width, int height) const override;--通过QPixmap从映射QImage中获取屏幕内容。

    QRegion doRedraw() override;--通过QPainter刷新frame buffer。

private:
    QStringList mArgs;
    int mFbFd;
    int mTtyFd;

    QImage mFbScreenImage;
    int mBytesPerLine;
    int mOldTtyMode;

    struct {
        uchar *data;
        int offset, size;
    } mMmap;

    QPainter *mBlitter;
};

QLinuxFbScreen::initialize()打开fb设备,从中获取屏幕参数,映射frame buffer内存,并以此创建QImage对象,为后续doRedraw()和grabWindow()做好准备。

bool QLinuxFbScreen::initialize()
{
    QRegularExpression ttyRx(QLatin1String("tty=(.*)"));
    QRegularExpression fbRx(QLatin1String("fb=(.*)"));
    QRegularExpression mmSizeRx(QLatin1String("mmsize=(\\d+)x(\\d+)"));
    QRegularExpression sizeRx(QLatin1String("size=(\\d+)x(\\d+)"));
    QRegularExpression offsetRx(QLatin1String("offset=(\\d+)x(\\d+)"));

    QString fbDevice, ttyDevice;
    QSize userMmSize;
    QRect userGeometry;
    bool doSwitchToGraphicsMode = true;

    // Parse arguments
    for (const QString &arg : qAsConst(mArgs)) {
        QRegularExpressionMatch match;
        if (arg == QLatin1String("nographicsmodeswitch"))
            doSwitchToGraphicsMode = false;
        else if (arg.contains(mmSizeRx, &match))
            userMmSize = QSize(match.captured(1).toInt(), match.captured(2).toInt());
        else if (arg.contains(sizeRx, &match))
            userGeometry.setSize(QSize(match.captured(1).toInt(), match.captured(2).toInt()));
        else if (arg.contains(offsetRx, &match))
            userGeometry.setTopLeft(QPoint(match.captured(1).toInt(), match.captured(2).toInt()));
        else if (arg.contains(ttyRx, &match))
            ttyDevice = match.captured(1);
        else if (arg.contains(fbRx, &match))
            fbDevice = match.captured(1);
    }

    if (fbDevice.isEmpty()) {--如果没有指定fb设备,下面选择默认设备。
        fbDevice = QLatin1String("/dev/fb0");
        if (!QFile::exists(fbDevice))
            fbDevice = QLatin1String("/dev/graphics/fb0");
        if (!QFile::exists(fbDevice)) {
            qWarning("Unable to figure out framebuffer device. Specify it manually.");
            return false;
        }
    }

    // Open the device
    mFbFd = openFramebufferDevice(fbDevice);--打开fb设备。
    if (mFbFd == -1) {
        qErrnoWarning(errno, "Failed to open framebuffer %s", qPrintable(fbDevice));
        return false;
    }

    // Read the fixed and variable screen information
    fb_fix_screeninfo finfo;
    fb_var_screeninfo vinfo;
    memset(&vinfo, 0, sizeof(vinfo));
    memset(&finfo, 0, sizeof(finfo));

    if (ioctl(mFbFd, FBIOGET_FSCREENINFO, &finfo) != 0) {--获取当前fb设备的固定信息。
        qErrnoWarning(errno, "Error reading fixed information");
        return false;
    }

    if (ioctl(mFbFd, FBIOGET_VSCREENINFO, &vinfo)) {--获取当前fb设备的可变信息,包括分辨率、像素位宽等等。
        qErrnoWarning(errno, "Error reading variable information");
        return false;
    }

    mDepth = determineDepth(vinfo);--得出当前fb设备的色深。
    mBytesPerLine = finfo.line_length;--获取一行数据长度。
    QRect geometry = determineGeometry(vinfo, userGeometry);
    mGeometry = QRect(QPoint(0, 0), geometry.size());
    mFormat = determineFormat(vinfo, mDepth);
    mPhysicalSize = determinePhysicalSize(vinfo, userMmSize, geometry.size());

    // mmap the framebuffer
    mMmap.size = finfo.smem_len;--frambuffer的大小。
    uchar *data = (unsigned char *)mmap(0, mMmap.size, PROT_READ | PROT_WRITE, MAP_SHARED, mFbFd, 0);--将内核中framebuffer映射为可读写,大小为finfo.smem_len大小的一块buffer。
    if ((long)data == -1) {
        qErrnoWarning(errno, "Failed to mmap framebuffer");
        return false;
    }

    mMmap.offset = geometry.y() * mBytesPerLine + geometry.x() * mDepth / 8;--预留一帧+一行大小的数据。
    mMmap.data = data + mMmap.offset;--QImage的buffer从mMmap.offset开始。

    QFbScreen::initializeCompositor();
    mFbScreenImage = QImage(mMmap.data, geometry.width(), geometry.height(), mBytesPerLine, mFormat);--QImage类是设备无关的图像,可以进行像素及操作,也可被用作绘图设备。

    mCursor = new QFbCursor(this);

    mTtyFd = openTtyDevice(ttyDevice);
    if (mTtyFd == -1)
        qErrnoWarning(errno, "Failed to open tty");

    switchToGraphicsMode(mTtyFd, doSwitchToGraphicsMode, &mOldTtyMode);
    blankScreen(mFbFd, false);--调用FBIOBLANK。

    return true;
}

3. Linux DRM和framebuffer驱动

make linux-menuconfig中进入Device Driver->Graphics support,打开Direct Rendering Manager和DRM Support for PL111 CLCD driver:

 pl111驱动注册framebuffer设备:

pl111_amba_driver
    ->pl111_amba_probe
        ->pl111_versatile_init
            ->pl111_vexpress_clcd_init
        ->drm_fbdev_generic_setup
            ->drm_client_init
            ->drb_fbdev_client_hotplug
                ->drm_fb_helper_initial_config
                    ->__drm_fb_helper_initial_config_and_unlock
                        ->register_framebuffer

dtb配置:

    clcd@10020000 {
        compatible = "arm,pl111", "arm,primecell";
        reg = <0x10020000 0x1000>;
        interrupt-names = "combined";
        interrupts = <0 44 4>;
        clocks = <&oscclk1>, <&oscclk2>;
        clock-names = "clcdclk", "apb_pclk";
        /* 1024x768 16bpp @65MHz */
        max-memory-bandwidth = <95000000>;

        port {
            clcd_pads_ct: endpoint {
                remote-endpoint = <&dvi_bridge_in_ct>;
                arm,pl11x,tft-r0g0b0-pads = <0 8 16>;
            };
        };
    };

Linux DRM子系统属于GPU一部分,更多参考《DRM子系统》。关于Framebuffer,更多参考《Frame Buffer Library — The Linux Kernel documentation》《Mine of Information - Linux Framebuffer Drivers (vonos.net)》《Linux Framebuffer 实验》。

Linux Graphics Drivers: an Introduction (people.freedesktop.org)》中介绍了Linux显示相关驱动,包括Framebuffer、DRM、OpenGL等。

 参考文档:

Qt for Embedded Linux | Qt 5.15