内容和参考
内容
之前学习了drm驱动的初始化,下边看drm用户态接口是怎么操作的
static const struct file_operations vkms_fops = { // 用户态操作接口
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
};
static struct drm_driver vkms_driver = {
.fops = &vkms_fops,
......
};
另外,学习了struct drm_driver 的部分字段是如何使用:
struct drm_driver {
// open 和 postclose 都是在drm_open的末尾 或者 drm_release 的 开始调用,分配程序专有
// 没有规定必须要实现,根据自己需要
int (*open) (struct drm_device *, struct drm_file *);
void (*postclose) (struct drm_device *, struct drm_file *);
void (*preclose) (struct drm_device *, struct drm_file *file_priv); // LEGACY,不用管
void (*lastclose) (struct drm_device *);
int (*dumb_create)(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args); // !!! 必须实现,DRM_IOCTL_MODE_CREATE_DUMB 接口使用
int (*dumb_map_offset)(struct drm_file *file_priv,
struct drm_device *dev, uint32_t handle,
uint64_t *offset); // !!! 可选实现,如果不实现则使用 drm_gem_create_mmap_offset()
int (*dumb_destroy)(struct drm_file *file_priv,
struct drm_device *dev,
uint32_t handle); // !!! 可选实现,如果不实现则使用drm_gem_dumb_destroy()
// 销毁用户使用dumb buffer, 只是将引用计数-1,为0时释放;
const struct drm_ioctl_desc *ioctls; // 如果用户有自定义ioctl命令,则必须要实现
int num_ioctls;
};
open接口
- 调用情景 : drm_open中会创建drm_file描述, 如果定义了 @open 接口 ,在打开新的 &struct drm_file 时的驱动程序回调。
- 作用: 对于设置驱动程序专用的数据结构(如缓冲区分配器,执行上下文或类似内容)很有用。
- 释放: 此类驱动程序专用资源必须在 @postclose中 再次释放。
由于DRM的显示/模式集只能完全由一个&struct drm_file拥有(请参阅&drm_file.is_master和&drm_device.master),因此永远不需要在此回调中设置任何与模式集相关的资源。 这样做将是驱动程序设计错误。
postclose接口
- 调用情景 : 关闭新的&struct drm_file时,驱动程序回调之一。
- 作用: 删除 @open中分配的驱动程序专用数据结构,例如缓冲区分配器,执行上下文或类似内容。
由于DRM的显示/模式集只能由一个&struct drm_file拥有(请参阅&drm_file.is_master和&drm_device.master),因此在此回调中永远不需要拆除任何与模式集相关的资源。 这样做将是驱动程序设计错误。
lastclose接口
- 调用情景: @postclose之后调用, 在关闭最后一个&struct drm_file并且当前没有用于&struct drm_device的用户空间客户端时调用。
- 作用:现驱动程序仅应使用此方法通过drm_fb_helper_restore_fbdev_mode_unlocked()强制还原fbdev帧缓冲区。 ```c // linux-git/drivers/gpu/drm/drm_fb_helper.c
void drm_fb_helper_lastclose(struct drm_device *dev) { drm_fb_helper_restore_fbdev_mode_unlocked(dev->fb_helper); }
<a name="e8qxY"></a>
### 参考<br />
- [何小龙-DRM 驱动程序开发(VKMS)](https://blog.csdn.net/hexiaolong2009/article/details/105180621)和 [最简单的DRM应用程序](https://blog.csdn.net/hexiaolong2009/article/details/83721242)
- [蜗窝Linux graphic subsytem-1](http://www.wowotech.net/graphic_subsystem/graphic_subsystem_overview.html) 和 [蜗窝Linux graphic subsytem-2](http://www.wowotech.net/linux_kenrel/dri_overview.html)
- [Linux DRM Internals](https://www.kernel.org/doc/html/v5.12/gpu/drm-internals.html)
<a name="gDESh"></a>
## drm软件接口
文章都是参考 Linux-5.8学习的内容
```bash
linux-5.8\drivers\gpu\drm\drm_drv.c
linux-5.8\drivers\gpu\drm\drm_file.c // !!! 本节重点
linux-5.8\drivers\gpu\drm\drm_ioctl.c // !!! 本节重点
linux-5.8\include\drm\drm_drv.h
drm_open
软件代码流程如下:
linux-5.8\drivers\gpu\drm\drm_file.c
drm_open
=> struct drm_minor *minor = drm_minor_acquire(iminor(inode)); // 根据次设备ID获取对应的drm_minor
=> drm_dev_get(minor->dev); // kref++;
=> drm_open_helper(filp, minor); // 分配 struct drm_file *priv; 并将file->private = private_data;也挂到struct drm_device *dev->filelist链表
=> struct drm_file *priv = drm_file_alloc(struct drm_minor *minor); // 分配 struct drm_file *priv;
=> struct drm_driver *driver->open(struct drm_device *dev, struct drm_file *file) !!!接口调用
drm_release
一般来说,都是open的反向操作
drm_ioctl
drm_ioctl分为以下几个细节:
首先 内核用struct drm_ioctl_desc 来描述一个cmd 以及它的处理函数;
struct drm_ioctl_desc {
unsigned int cmd;
enum drm_ioctl_flags flags;
drm_ioctl_t *func;
const char *name;
};
其次,CMD在 0x40-0xA0间是用户自定义命令,0x00-0x40是内核默认的处理
#define DRM_COMMAND_BASE 0x40
#define DRM_COMMAND_END 0xA0
struct drm_driver {
const struct drm_ioctl_desc *ioctls; // 用户自定义ioctl命令
int num_ioctls; // 用户自定义ioctl的command个数
};
CMD在0x00-0x40 使用内核的ioctl处理 drm_ioctls;
- CMD在0x40-0xA0使用drm_driver中定义的ioctls进行处理;
代码实现如下:
linux-5.8\drivers\gpu\drm\drm_ioctl.c
drm_ioctl
=> ioctl = &dev->driver->ioctls[index] ? ioctl = &drm_ioctls[nr]; // !!!判断命令所属区间,从获取CMD对应的struct drm_ioctl_desc
=> // !!! 分配传输传出buffer: 根据CMD获取参数长度,如果size<128,用栈空间就好,如果size>128,需要malloc;
=> copy_from_user(kdata, (void __user *)arg, in_size); // 先将用户态数据拷贝到buffer ???问题: in_size == 0 返回什么???
=> drm_ioctl_kernel(filp, func, kdata, ioctl->flags); // struct file *filp, func = drm_ioctl_desc->func;
// ioctl->flags = drm_ioctl_desc->flags
// 分配的缓存buffer
=> drm_ioctl_permit(u32 flags, struct drm_file *file_priv); // 检测flags 与 drm_file的属性,暂时不用关心
=> func(dev, kdata, file_priv);
=> copy_to_user((void __user *)arg, kdata, out_size); // 将数据返回用户态 ???问题: out_size == 0返回什么???
drm应用流程
最简单的drm操作
参考: 最简单的DRM应用程序
来分析DRM都做了哪些步骤:
int main(int argc, char **argv)
{
/* open the drm device */
open("/dev/dri/card0");
/* get crtc/encoder/connector id */
drmModeGetResources(...);
/* get connector for display mode */
drmModeGetConnector(...);
/* create a dumb-buffer */
drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB);
/* bind the dumb-buffer to an FB object */
drmModeAddFB(...);
/* map the dumb buffer for userspace drawing */
drmIoctl(DRM_IOCTL_MODE_MAP_DUMB);
mmap(...);
/* start display */
drmModeSetCrtc(crtc_id, fb_id, connector_id, mode);
}
驱动要运行必须支持以下特性:
- DRM驱动支持MODESET;
- DRM驱动支持dumb-buffer(即连续物理内存);
- DRM驱动至少支持1个CRTC,1个Encoder,1个Connector;
- DRM驱动的Connector至少包含1个有效的drm_display_mode。
open
参考前边的drm_open
get_resource
这个函数是 获取 struct drm_device 中的 struct drm_mode_config mode_config 信息, 以及获取 struct drm_file 里边的 struct list_head fbs 信息
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, 0),
struct drm_mode_card_res {
__u64 fb_id_ptr; // struct drm_file *file->struct list_head fbs;
__u64 crtc_id_ptr; // drm_crtc_crc_init
__u64 connector_id_ptr; // drm_connector_init
__u64 encoder_id_ptr; // drm_encoder_init
__u32 count_fbs;
__u32 count_crtcs;
__u32 count_connectors;
__u32 count_encoders;
__u32 min_width;
__u32 max_width;
__u32 min_height;
__u32 max_height;
};
drm_mode_config 相关参数如下:
struct drm_mode_config {
int num_fb;
struct list_head fb_list;
int num_connector;
struct ida connector_ida;
struct list_head connector_list;
int num_encoder;
struct list_head encoder_list;
int num_total_plane;
struct list_head plane_list;
int num_crtc;
struct list_head crtc_list;
struct list_head property_list;
......
};
get_connector
这部分实现比较简单,但 drm_mode_getconnector 后期学习完kms参数后再回顾
用户态 从resouce中选择一个 connector_id 作为参数,来获取当前connector
drm_public drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connector_id); // libdrm调用接口
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, 0), // 驱动响应接口
struct drm_mode_get_connector {
......
__u32 connector_id; // !!!传入参数,从前边get_resource 中的 connector_id_ptr 选择需要显示的那个
......
};
create_dumb
调用用户自定义的 struct drm_driver *driver->dumb_create接口 来分配一个DUMB buffer;
struct bo * bo_create(int fd, unsigned int format,
unsigned int width, unsigned int height,
unsigned int handles[4], unsigned int pitches[4],
unsigned int offsets[4], enum util_fill_pattern pattern); // 用户态参考代码
static struct bo *
bo_create_dumb(int fd, unsigned int width, unsigned int height, unsigned int bpp); // bo_create 调用 bo_create_dumb 来创建一个dumb buffer
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_DUMB, drm_mode_create_dumb_ioctl, 0), // 驱动响应接口
// !!!调用自定义的dumb_create
struct drm_driver *driver->dumb_create(file_priv, dev, args);
addfb
使用gem申请一个framebuffer,添加到drm_file->fbs 链表中
drm_public int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth,
uint8_t bpp, uint32_t pitch, uint32_t bo_handle,
uint32_t *buf_id); // 用户态 申请framebuffer接口
DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb_ioctl, 0), // 驱动响应接口 drm_mode_addfb_ioctl->drm_mode_addfb
这里重点是 dev->mode_config.funcs->fb_create(dev, file_priv, r);
在之前说过, drm_dev_register 需要初始化 struct drm_device *dev->mode_config,其中包含了funcs
struct drm_mode_config {
...
const struct drm_mode_config_funcs *funcs;
resource_size_t fb_base;
};
struct drm_mode_config_funcs {
// 创建framebuffer对象
struct drm_framebuffer *(*fb_create)(struct drm_device *dev,
struct drm_file *file_priv,
const struct drm_mode_fb_cmd2 *mode_cmd); // 支持GEM 可直接使用 drm_gem_fb_create
}
参考ARM mali实现
malidp_fb_create
{
......
return drm_gem_fb_create(dev, file, mode_cmd); // 直接调用drm_gem_fb_create 即可
}
static const struct drm_mode_config_funcs malidp_mode_config_funcs = {
.fb_create = malidp_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static int malidp_init(struct drm_device *drm)
{
drm->mode_config.funcs = &malidp_mode_config_funcs;
}
map_dumb
前边分配了fb,但用户要使用必须将GEM分配的framebuffer映射到用户空间**,这样可以直接使用显存而避免了多次数据间的拷贝;**
static int bo_map(struct bo *bo, void **out); // 参考 libdrm 中的bo_map实现
drmIoctl(bo->fd, DRM_IOCTL_MODE_MAP_DUMB, &arg);
map = drm_mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED,
bo->fd, arg.offset);
DRM_IOCTL_DEF(DRM_IOCTL_MODE_MAP_DUMB, drm_mode_mmap_dumb_ioctl, 0), // 驱动响应接口
drm_mode_mmap_dumb_ioctl // 根据用户层 DRM_IOCTL_MODE_CREATE_DUMB 获取到的handle,查找对应gem的 fake offset,给map使用
if (dev->driver->dumb_map_offset) // !!!前边说过,drm_driver的dumb_map_offset是可选实现,默认用drm_gem_dumb_map_offset
return dev->driver->dumb_map_offset(file_priv, dev,
args->handle,
&args->offset);
else
return drm_gem_dumb_map_offset(file_priv, dev, args->handle,
&args->offset);
因为framebuffer是通过gem申请的显存空间,但用户使用必须要mmap到用户地址空间来进行数据填充,怎么办?
内核对每个gem buffer用offset描述, 根据fb的handle来查找到对应的offset,给用户空间mmap。
关于GEM的部分,后期详细讲述;
set_crtc
将connector,crtc, fb绑定在一起,开始出图
drm_public int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
uint32_t x, uint32_t y, uint32_t *connectors, int count,
drmModeModeInfoPtr mode);
=> DRM_IOCTL(fd, DRM_IOCTL_MODE_SETCRTC, &crtc);
drm_mode_setcrtc
// crtc_req->mode_valid 这个依赖于 drmModeGetConnector 获取的conn->modes[x]; // 依赖connector的mode属性
关于crtc部分 在学习KMS后再详解这部分
destroyfb
删除之前分配的framebuffer
void kms_framebuffer_free(struct kms_framebuffer *fb); // 参考libdrm中 kms_framebuffer_free 流程
drmModeRmFB(device->fd, fb->id);
DRM_IOCTL(fd, DRM_IOCTL_MODE_RMFB, &bufferId);
drmIoctl(device->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &args);
DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb_ioctl, 0), // 驱动响应接口, 从配置中删除一个framebuffer
DRM_IOCTL_DEF(DRM_IOCTL_MODE_DESTROY_DUMB, drm_mode_destroy_dumb_ioctl, 0), // // 销毁用户使用dumb buffer, 只是将引用计数-1,为0时释放;
drm_mode_rmfb_ioctl
drm_mode_rmfb(dev, *fb_id, file_priv);
drm_mode_destroy_dumb_ioctl
drm_mode_destroy_dumb(dev, args->handle, file_priv);
if (dev->driver->dumb_destroy)
return dev->driver->dumb_destroy(file_priv, dev, handle); // 默认用用户自定义的销毁接口
else
return drm_gem_dumb_destroy(file_priv, dev, handle);
freeconnector
这部分没有内核层操作,只是吧用户空间申请的buffer释放了;
// libdrm释放接口,释放libdrm 中申请的缓存
drm_public void drmModeFreeConnector(drmModeConnectorPtr ptr)
{
drmFree(ptr->encoders);
drmFree(ptr->prop_values);
drmFree(ptr->props);
drmFree(ptr->modes);
drmFree(ptr);
}
freeresource
这部分没有内核层操作,只是吧用户空间申请的buffer释放了;
drm_public void drmModeFreeResources(drmModeResPtr ptr)
{
drmFree(ptr->fbs);
drmFree(ptr->crtcs);
drmFree(ptr->connectors);
drmFree(ptr->encoders);
drmFree(ptr);
}
close
参考前边的 drm_release