Edit online

设计说明

30 Oct 2024
Read time: 16 minute(s)

源码说明

本模块源代码目录结构如下

HAL 层实现:

luban-lite/bsp/artinchip/hal/ge
├── hal_ge_normal.c // normal 模式驱动
├── hal_ge_hw.c     // normal 模式对硬件操作的封装
├── hal_ge_hw.h     // normal 模式对硬件操作封装的 API
├── hal_ge_cmdq.c   // CMD queue 模式驱动
├── hal_ge_reg.h    // GE 寄存器定义
└── ../include/uapi/mpp_types.h  // mpp 公用的类型定义

Driver 层实现:

luban-lite/bsp/artinchip/drv/ge
├── aic_drv_ge.c.c  // GE 驱动接口
└── Kconfig

MPP 层实现:

luban-lite/packages/artinchip/mpp/ge
├── cmdq_ops.c       // MPP GE 驱动命令队列封装
├── normal_ops.c     // MPP GE 驱动普通模式封装
├── mpp_ge.c         // MPP GE 操作封装实现
├── include/ge_ops.h // MPP GE 操作函数定义
├── include/ge_reg.h // GE 寄存器定义
└── ../include/mpp_ge.h // MPP GE 操作封装定义

软件架构

在 GE 驱动框架中,非命令队列模式(normal 模式)和命令队列模式(cmdq 模式)是两种不同的操作模式。GE 驱动框架分为以下几个层级:
注: 关于两种操作模式的详细说明,可分别查看非命令队列模式命令队列模式
  • GE HAL :在 normal 模式下,GE HAL 层具有完整的功能,可以独立运行。在 cmdq 模式下,GE HAL 层负责 GE 硬件资源初始化和获取等,必须依靠 MPP 层实现完整功能。

  • GE DRV:提供对 HAL 层的封装,简化了接口调用。

  • MPP:在 normal 模式下,MPP 层仅调用 GE HAL 的接口。在 cmdq 模式下,MPP 层不仅调用 GE HAL 层的接口,而且还包含命令的准备、封装和发送。

  • APP:应用层通过调用 MPP 层接口实现对 GE 的操作。


../../../../_images/ge_sw_framwork.png

1. GE 驱动框架

非命令队列模式

非命令队列模式,即 normal 模式。在 GE normal 模式下,GE HAL 层已经实现了 GE 的完整功能,可以不依靠 MPP 层和 DRV 层进行运行。 但在一般情况下,不会直接操作 HAL 层,而是通过 MPP 层间接操作 GE。

非命令队列模式可用 hal_ge_control 控制 GE,包括以下几种操作:

  • IOC_GE_VERSION

  • IOC_GE_MODE

  • IOC_GE_FILLRECT

  • IOC_GE_BITBLT

  • IOC_GE_ROTATE

在 normal 模式下,对接口 IOC_GE_FILLRECTIOC_GE_BITBLTIOC_GE_ROTATE 的调用是同步的,即硬件执行任务完成后接口调用才会返回。

关键流程设计
在 normal 模式下,GE 驱动各种功能都是通过 hal_ge_control 调用来实现,每一次 hal_ge_control 的调用都包括以下步骤:
  1. GE 参数配置。
  2. GE 模块中断开启。
  3. GE 硬件启动。
  4. GE 等待中断,当前线程进入等待状态,直到收到中断通知。
  5. 硬件完成任务后,关闭中断。

hal_ge_control 通过 mutex 保护,确保多线程环境下的安全性。在 normal 模式下,支持多应用同时打开驱动,并调用 hal_ge_control


../../../../_images/ge_sw_normal_1.png

2. Normal 模式中断流程

等待中断流程:

  • 在调用 hal_ge_init 时候创建事件集: data → wait = aicos_event_create()

  • hal_ge_control 中调用如下函数,使当前应用在等待事件中睡眠:

    ret = aicos_event_recv(data->wait, HW_RUNNING_EVENT, &recved, GE_TIMEOUT)。
  • 在中断服务程序中调用 aicos_event_send(data->wait, HW_RUNNING_EVENT),激活等待事件中睡眠的应用。

命令队列模式

命令队列模式,即 CMD queue 模式,是一种高效的图形处理方式,允许多个任务以批处理的方式执行,从而提升整体性能。

在 CMD queue 模式下,GE 驱动内部是以 command queue 的方式执行的, GE HAL 层只负责从应用接收命令队列后执行命令队列。GE 的完整功能需要依赖 MPP 中间件, 在 MPP 中间件中会把用户设置的参数信息转换成硬件可以识别的命令队列信息。

Command Queue 相关的几个概念:

  • Task:Graphics Engine (GE) 可以执行的最小任务单元,比如说一次 blit 操作或一次矩形填充。

  • Batch:硬件以 batch 为单位执行,是一系列命令的集合,可以包含一个或者多个 task,软件也必须以 batch 为单位向驱动写入命令。

  • Command Queue:软件可以向 Command Queue 写入多个 batch,硬件以 batch 为单位,按顺序执行。
    注: GE Command Queue 是以 ring buffer 的方式实现的, 关于 ring buffer 的说明,请参考 GE 规格书。

CMD Queue 模式应用可使用 hal_ge_control结构,包括以下操作:

  • IOC_GE_VERSION

  • IOC_GE_MODE

  • IOC_GE_CMD_BUF_SIZE

  • IOC_GE_SYNC

CMD queue 模式与 normal 模式的区别

和 normal 模式驱动相比,CMD queue 增加了 hal_ge_write 接口,命令队列通过 hal_ge_write 接口,以 batch 为单位发送给驱动。一个 batch 中可以包含多个 task 的命令。 hal_ge_write 操作是异步的,相应的命令只要写入驱动中的 cmd queue buffer 即返回,不用等待硬件执行完当前 batch 中的所有命令。当应用需要等待发送的命令执行完成时可以调用 IOC_GE_SYNC 接口。

在 CMD queue 模式下,通过 hal_ge_write 接口写入以 batch 为单位的命令, 硬件可以连续执行多个 task。而在 normal 模式下,通过 IOC_GE_BITBLT 等接口,硬件一次只能执行一个任务。

初始化流程

GE 驱动的初始化过程见 hal_ge_init() 函数,除了申请中断外, 还申请了存储 Command Queue 需要的 ring buffer,以及存储 batch 信息的结构体。

Batch 结构体不存储实际的命令,其中保存了一段指向 ring buffer 的空间,包括相对于 ring buffer 起始地址的 offset,以及当前 batch 中的命令 length。batch 结构体定义如下:
struct aic_ge_batch {
    struct list_head list;
    int offset;
    int length;
    int client_id;
};

下图 ring buffer 中不同的颜色代表不同的 batch 对应的命令:


../../../../_images/ge_function_cmdq_01.png

3. ring buffer 与 batch 关系图
目前驱动中定义了 8 个存储 batch 信息的结构体,ring buffer 的 size 定义为 32K, 32K 的空间可以缓存超过 256 个 task(假如都是 RGB 格式的 task)。
#define MAX_BATCH_NUM 8
#define CMD_BUF_SIZE  AIC_GE_CMDQ_BUF_LENGTH

AIC_GE_CMDQ_BUF_LENGTH 在 Kconfig 中定义,可以在 menuconfig 去分配 CMD_BUF_SIZE 大小

batch 管理
每一个 batch 总共可能存在三种状态:
  • free 状态,batch 中没有可用信息在 free batch list 中。

  • ready 状态,batch 中有等待执行的 cmd 信息在 ready list batch 中。

  • 运行状态,当前硬件正在运行的 batch 既 不在 free list 中也不在 ready list 中。

Batch 状态变化流程如下:

  1. 在分配给应用的缓冲 buffer 中,应用组织好命令队列,以 batch 为单位,通过标准的 write 接口把命令拷贝到 ring buffer 中。

  2. 在驱动中维护一个包含每个 batch 起始 offset 和 length 信息的链表,硬件以 batch 为单位执行命令队列。


    ../../../../_images/ge_function_cmdq_4.png

    4. batch 状态变化流程图
  3. 如上图所示,会有两个地方启动硬件执行 batch 命令
    • 当应用调用了 write 命令写入当前 batch 信息,并且硬件处于空闲状态。

    • 在中断服务判断当前的 ready list batch 不为空,则从列表中 dequeue 一个 batch,送给硬件执行。

多线程支持

Luban-Lite 中,应用是在线程去调用 GE 的。为了确保不同线程添加的 batch 按照先进先出原则运行,系统需要对多线程进行有效管理。


../../../../_images/ge_function_cmdq_3.png

5. 多线程支持

当某个线程需要等待自己 batch 是否完成时,需要调用 IOC_GE_SYNC 命令,等待自己所有的 batch 执行完成即可,如上图所示。 当线程 2 调用 IOC_GE_SYNC 命令时,只需等待 batch5、batch3、batch2、batch0 完成即可,不用处理后续加入的 batch6、batch7。

当某个线程需要使用 GE 时,IOC_GE_SYNC 的实现流程如下:

  1. 通过 hal_open 接口创建当前应用的 client,并添加到 client 链表中。每个 client 都有一个唯一的识别 ID。
    struct aic_ge_client {
        struct list_head list;
        struct list_head buf_list;
        int id;
        int batch_num;
    };
  2. 当前应用调用 hal_write 接口写入一个 batch 命令时, 当前 batch 中的 client_id 会写入对应的 client 识别 ID, 并且对应的 client 中的 batch_num 引用计数会加 1。

  3. 硬件每执行完成一个 batch 产生一次中断,在中断服务程序中查询当前 batch 中的 client_id,并通过 client_id 从 client 链表中找到当前 client, 对应的 client 中的 batch_num 引用计数减 1。同时,通知所有打开了 GE 的应用
    aicos_event_send(data->wait_event, HW_RUNNING_EVENT);
  4. 应用通过接口 IOC_GE_SYNC 等待任务完成,只需要等待当前 client 中的 batch_num 为 0 即可。
    static int ge_client_sync(struct aic_ge_data *data,
                    struct aic_ge_client *client)
    {
        int ret = 0;
        uint32_t recved;
    
        hal_log_debug("%s\n", __func__);
    
        while (client->batch_num) {
            ret = aicos_event_recv(data->wait_event,
                                    BATCH_NUM_EVENT,
                                    &recved,
                                    GE_TIMEOUT);
            if (ret < 0) {
                break;
            }
        }
    
        hal_log_debug("%s\n", __func__);
    
        return ret;
    }

数据结构设计

enum ge_pd_rules
Porter/Duff alpha 混合规则枚举
/*
 * enum ge_pd_mode - graphics engine Porter/Duff alpha blending rules
 *
 * pixel = (source * fs + destination * fd)
 * sa = source alpha
 * da = destination alpha
 *
 * @GE_PD_NONE:           fs: sa      fd: 1.0-sa (defaults)
 * @GE_PD_CLEAR:          fs: 0.0     fd: 0.0
 * @GE_PD_SRC:            fs: 1.0     fd: 0.0
 * @GE_PD_SRC_OVER:       fs: 1.0     fd: 1.0-sa
 * @GE_PD_DST_OVER:       fs: 1.0-da  fd: 1.0
 * @GE_PD_SRC_IN:         fs: da      fd: 0.0
 * @GE_PD_DST_IN:         fs: 0.0     fd: sa
 * @GE_PD_SRC_OUT:        fs: 1.0-da  fd: 0.0
 * @GE_PD_DST_OUT:        fs: 0.0     fd: 1.0-sa
 * @GE_PD_SRC_ATOP:       fs: da      fd: 1.0-sa
 * @GE_PD_DST_ATOP:       fs: 1.0-da  fd: sa
 * @GE_PD_ADD:            fs: 1.0     fd: 1.0
 * @GE_PD_XOR:            fs: 1.0-da  fd: 1.0-sa
 * @GE_PD_DST:            fs: 0.0     fd: 1.0
 */
enum ge_pd_rules {
        GE_PD_NONE           =  0,
        GE_PD_CLEAR          =  1,
        GE_PD_SRC            =  2,
        GE_PD_SRC_OVER       =  3,
        GE_PD_DST_OVER       =  4,
        GE_PD_SRC_IN         =  5,
        GE_PD_DST_IN         =  6,
        GE_PD_SRC_OUT        =  7,
        GE_PD_DST_OUT        =  8,
        GE_PD_SRC_ATOP       =  9,
        GE_PD_DST_ATOP       = 10,
        GE_PD_ADD            = 11,
        GE_PD_XOR            = 12,
        GE_PD_DST            = 13,
};
struct ge_ctrl
GE 控制结构体
/**
 * struct ge_ctrl - ge ctrl functions
 * @alpha_en
 *  0: enable Porter/Duff alpha blending
 *  1: disable Porter/Duff alpha blending
 * @alpha_rules: Porter/Duff alpha blending rules
 * @src_alpha_mode: source alpha mode
 *  0: pixel alpha mode(src_alpha = src_pixel_alpha)
 *  1: global alpha mode(src_alpha = src_global_alpha)
 *  2: mixded alpha mode(src_alpha = src_pixel_alpha * src_global_alpha / 255)
 * @src_global_alpha: source global alpha value (0~255)
 *  used by global alpha mode and mixded alpha mode
 * @dst_alpha_mode: destination alpha mode
 *  0: pixel alpha mode(dst_alpha = dst_pixel_alpha)
 *  1: global alpha mode(dst_alpha = dst_global_alpha)
 *  2: mixded alpha mode(dst_alpha = dst_pixel_alpha * dst_global_alpha / 255)
 * @dst_global_alpha: destination global alpha value (0~255)
 *  used by global alpha mode and mixed alpha mode
 * @ck_en
 *  0: disable color key
 *  1: enable color key
 * @ck_value: rgb value of color key to match the source pixels
 *  bit[31:24]: reserved
 *  bit[23:16]: R value
 *  bit[15:8]: G value
 *  bit[7:0]: B value
 * @dither_en(Not supported by IOC_GE_ROTATE)
 *  0: disable dither
 *  1: enable dither
 * @flags: the flags of some functions, such as scan order, src H/V flip
 *         and src 90/180/270 degree rotation, the H flip, V flip
 *         and rotation can be enabled at the same time, the effect
 *         of flip is in front of rotation, only supported by IOC_GE_BITBLT
 *         the flags was defined in mpp_types.h
 */
struct ge_ctrl {
        unsigned int       alpha_en;
        enum ge_pd_rules   alpha_rules;
        unsigned int       src_alpha_mode;
        unsigned int       src_global_alpha;
        unsigned int       dst_alpha_mode;
        unsigned int       dst_global_alpha;
        unsigned int       ck_en;
        unsigned int       ck_value;
        unsigned int       dither_en;
        unsigned int       flags;
};
enum ge_fillrect_type
颜色填充类型枚举
/*
 * enum ge_fillrect_type - the ge fill rectangle types:
 *
 * GE_NO_GRADIENT: No gradient is used, only use start_color to
 *                  fill rectangle, ignore end_color
 * GE_H_LINEAR_GRADIENT: Interpolates colors between start_color
 *                  and end_color in the horizontal direction
 *                  form left to right
 * GE_V_LINEAR_GRADIENT: Interpolates colors between start_color and
 *                  end_color in the vertical direction from top to
 *                  buttom
 */
enum ge_fillrect_type {
        GE_NO_GRADIENT         = 0,
        GE_H_LINEAR_GRADIENT   = 1,
        GE_V_LINEAR_GRADIENT   = 2,
};
struct ge_fillrect
矩形填充结构体
/**
 * struct ge_fillrect - ge fill rectangle
 * @type: fill rect type
 * @start_color: start color(32 bits)
 * bit[31:24] alpha value
 * bit[23:16] r value
 * bit[15:8]  g value
 * bit[7:0]   b value
 * @end_color: end color(32 bits)
 * bit[31:24] alpha value
 * bit[23:16] r value
 * bit[15:8]  g value
 * bit[7:0]   b value
 * @dst_buf: the destination buffer
 * @ctrl: ge ctrl functions
 */
struct ge_fillrect {
        enum ge_fillrect_type  type;
        unsigned int           start_color;
        unsigned int           end_color;
        struct mpp_buf         dst_buf;
        struct ge_ctrl         ctrl;
};
struct ge_bitblt
位块搬移结构体
/**
 * struct ge_bitblt - ge bitblt
 * @src_buf: the source buffer
 * @dst_buf: the destination buffer
 * @ctrl: ge ctrl functions
 */
struct ge_bitblt {
        struct mpp_buf   src_buf;
        struct mpp_buf   dst_buf;
        struct ge_ctrl   ctrl;
};
struct ge_bitblt
位块搬移结构体
/**
 * struct ge_rotation - ge rotation
 * @src_buf: the source buffer
 * @dst_buf: the destination buffer
 * @src_rot_center: left-top x/y coordinate of src center
 * @dst_rot_center: left-top x/y coordinate of dst center
 * @angle_sin: 2.12 fixed point, the sin value of rotation angle
 * @angle_cos: 2.12 fixed point, the cos value of rotation angle
 * @ctrl: ge ctrl functions
 */
struct ge_rotation {
        struct mpp_buf        src_buf;
        struct mpp_buf        dst_buf;
        struct mpp_point      src_rot_center;
        struct mpp_point      dst_rot_center;
        int                   angle_sin;
        int                   angle_cos;
        struct ge_ctrl        ctrl;
};
enum ge_mode

GE 模式枚举

enum ge_mode {
        GE_MODE_NORMAL,
        GE_MODE_CMDQ,
};

接口设计

应用通过 hal_ge_open 打开 GE 驱动。

1. IOC_GE_VERSION
接口语法:
int hal_ge_control(struct aic_ge_client *clt, unsigned long cmd, unsinged int *pversion);
功能说明 获取 GE 版本
参数
cmd:IOC_GE_VERSION
pversion: 指向 32bits 无符号版本号指针
返回值
0:成功
<0:失败
注意事项
2. IOC_GE_MODE
接口语法:
int hal_ge_control(struct aic_ge_client *clt, unsigned long cmd, enum ge_mode *mode);
功能说明 获取工作模式
参数
cmd:IOC_GE_MODE
mode: enum ge_mode 指针
返回值
0:成功
<0:失败
注意事项
3. IOC_GE_FILLRECT
接口语法:
int hal_ge_control(struct aic_ge_client *clt, unsigned long cmd, struct ge_fillrect *fill);
功能说明 矩形填充
参数
cmd:IOC_GE_FILLRECT
fill: 指向 struct ge_fillrect 指针
返回值
0:成功
<0:失败
注意事项 仅供 normal 模式使用, cmd queue 模式不可用
4. IOC_GE_BITBLT
接口语法:
int hal_ge_control(struct aic_ge_client *clt, unsigned long cmd, struct ge_bitblt *bitblt);
功能说明 位块搬移
参数
cmd:IOC_GE_BITBLT
bitblt: 指向 struct ge_bitblt 指针
返回值
0:成功
<0:失败
注意事项 仅供 normal 模式使用, cmd queue 模式不可用
5. IOC_GE_ROTATE
接口语法:
int hal_ge_control(struct aic_ge_client *clt, unsigned long cmd, struct ge_rotation *rot);
功能说明 任意角度旋转
参数
cmd:IOC_GE_ROTATE
rot: 指向 struct ge_rotation 指针
返回值
0:成功
<0:失败
注意事项 仅供 normal 模式使用, cmd queue 模式不可用
6. IOC_GE_SYNC
接口语法:
int hal_ge_control(struct aic_ge_client *clt, unsigned long cmd);
功能说明 等待任务完成
参数
cmd:IOC_GE_SYNC
返回值
0:成功
<0:失败
注意事项
7. IOC_GE_CMD_BUF_SIZE
接口语法:
int ioctl(int fd, unsigned long cmd, unsinged int *size);
功能说明 获取 cmd buffer size
参数
cmd:IOC_GE_CMD_BUF_SIZE
size: 指向 32bits 无符号数指针
返回值
0:成功
<0:失败
注意事项 cmd queue 模式可用,normal 模式不可用

MPP 对 GE 接口的封装

设计说明中 MPP GE 设计及接口说明。