1 // SPDX-License-Identifier: (GPL-2.0+ OR MIT)
2 /*
3 * Copyright (c) 2021 Rockchip Electronics Co., Ltd.
4 * Author: Sandy Huang <hjc@rock-chips.com>
5 */
6
7 #include <drm/drm_atomic_uapi.h>
8 #include <drm/drm_drv.h>
9 #include <drm/drm_file.h>
10 #include <drm/drm_gem_cma_helper.h>
11 #include <drm/drm_of.h>
12 #include <drm/drm_probe_helper.h>
13
14 #include <linux/file.h>
15
16 #include "rockchip_drm_drv.h"
17 #include "rockchip_drm_debugfs.h"
18 #include "rockchip_drm_fb.h"
19
20 #define DUMP_BUF_PATH "/data"
21 #define AFBC_HEADER_SIZE 16
22 #define AFBC_HDR_ALIGN 64
23 #define AFBC_SUPERBLK_PIXELS 256
24 #define AFBC_SUPERBLK_ALIGNMENT 128
25
26 #define to_rockchip_crtc(x) container_of(x, struct rockchip_crtc, crtc)
27
temp_pow(int sum,int n)28 static int temp_pow(int sum, int n)
29 {
30 int i;
31 int temp = sum;
32
33 if (n < 1)
34 return 1;
35 for (i = 1; i < n ; i++)
36 sum *= temp;
37 return sum;
38 }
39
get_afbc_size(uint32_t width,uint32_t height,uint32_t bpp)40 static int get_afbc_size(uint32_t width, uint32_t height, uint32_t bpp)
41 {
42 uint32_t h_alignment = 16;
43 uint32_t n_blocks;
44 uint32_t hdr_size;
45 uint32_t size;
46
47 height = ALIGN(height, h_alignment);
48 n_blocks = width * height / AFBC_SUPERBLK_PIXELS;
49 hdr_size = ALIGN(n_blocks * AFBC_HEADER_SIZE, AFBC_HDR_ALIGN);
50
51 size = hdr_size + n_blocks * ALIGN(bpp * AFBC_SUPERBLK_PIXELS / 8, AFBC_SUPERBLK_ALIGNMENT);
52
53 return size;
54 }
55
rockchip_drm_dump_plane_buffer(struct vop_dump_info * dump_info,int frame_count)56 int rockchip_drm_dump_plane_buffer(struct vop_dump_info *dump_info, int frame_count)
57 {
58 int flags;
59 int bpp = 32;
60 const char *ptr;
61 char file_name[100];
62 int width;
63 size_t size, uv_size = 0;
64 void *kvaddr;
65 struct file *file;
66 loff_t pos = 0;
67 struct drm_format_name_buf format_name;
68 char format[8];
69
70 drm_get_format_name(dump_info->format->format, &format_name);
71 strscpy(format, format_name.str, 5);
72 bpp = rockchip_drm_get_bpp(dump_info->format);
73
74 if (dump_info->yuv_format) {
75 u8 hsub = dump_info->format->hsub;
76 u8 vsub = dump_info->format->vsub;
77
78 width = dump_info->pitches;
79 flags = O_RDWR | O_CREAT | O_APPEND;
80 uv_size = (width * dump_info->height * bpp >> 3) * 2 / hsub / vsub;
81 snprintf(file_name, 100, "%s/video%d_%d_%s.%s", DUMP_BUF_PATH,
82 width, dump_info->height, format,
83 "bin");
84 } else {
85 width = dump_info->pitches * 8 / bpp;
86 flags = O_RDWR | O_CREAT;
87 snprintf(file_name, 100, "%s/win%d_area%d_%dx%d_%s%s%d.%s",
88 DUMP_BUF_PATH, dump_info->win_id,
89 dump_info->area_id, width, dump_info->height,
90 format, dump_info->AFBC_flag ?
91 "_AFBC_" : "_", frame_count, "bin");
92 }
93 kvaddr = vmap(dump_info->pages, dump_info->num_pages, VM_MAP,
94 pgprot_writecombine(PAGE_KERNEL));
95 if (!kvaddr)
96 DRM_ERROR("failed to vmap() buffer\n");
97 else
98 kvaddr += dump_info->offset;
99
100 if (dump_info->AFBC_flag)
101 size = get_afbc_size(width, dump_info->height, bpp);
102 else
103 size = (width * dump_info->height * bpp >> 3) + uv_size;
104
105 ptr = file_name;
106 file = filp_open(ptr, flags, 0644);
107 if (!IS_ERR(file)) {
108 kernel_write(file, kvaddr, size, &pos);
109 DRM_INFO("dump file name is:%s\n", file_name);
110 fput(file);
111 } else {
112 DRM_INFO("open %s failed\n", ptr);
113 }
114 vunmap(kvaddr);
115
116 return 0;
117 }
118
rockchip_drm_dump_buffer_show(struct seq_file * m,void * data)119 static int rockchip_drm_dump_buffer_show(struct seq_file *m, void *data)
120 {
121 seq_puts(m, " echo dump > dump to dump one frame\n");
122 seq_puts(m, " echo dumpon > dump to start vop keep dumping\n");
123 seq_puts(m, " echo dumpoff > dump to stop keep dumping\n");
124 seq_puts(m, " echo dumpn > dump n is the number of dump times\n");
125 seq_puts(m, " dump path is /data\n");
126
127 return 0;
128 }
129
rockchip_drm_dump_buffer_open(struct inode * inode,struct file * file)130 static int rockchip_drm_dump_buffer_open(struct inode *inode, struct file *file)
131 {
132 struct drm_crtc *crtc = inode->i_private;
133
134 return single_open(file, rockchip_drm_dump_buffer_show, crtc);
135 }
136
137 static ssize_t
rockchip_drm_dump_buffer_write(struct file * file,const char __user * ubuf,size_t len,loff_t * offp)138 rockchip_drm_dump_buffer_write(struct file *file, const char __user *ubuf,
139 size_t len, loff_t *offp)
140 {
141 struct seq_file *m = file->private_data;
142 struct drm_crtc *crtc = m->private;
143 char buf[14] = {};
144 int dump_times = 0;
145 struct vop_dump_list *pos, *n;
146 int i = 0;
147 struct rockchip_crtc *rockchip_crtc = to_rockchip_crtc(crtc);
148
149 if (!rockchip_crtc->vop_dump_list_init_flag)
150 return -EPERM;
151
152 if (len > sizeof(buf) - 1)
153 return -EINVAL;
154 if (copy_from_user(buf, ubuf, len))
155 return -EFAULT;
156 buf[len - 1] = '\0';
157 if (strncmp(buf, "dumpon", 6) == 0) {
158 rockchip_crtc->vop_dump_status = DUMP_KEEP;
159 DRM_INFO("keep dumping\n");
160 } else if (strncmp(buf, "dumpoff", 7) == 0) {
161 rockchip_crtc->vop_dump_status = DUMP_DISABLE;
162 DRM_INFO("close keep dumping\n");
163 } else if (strncmp(buf, "dump", 4) == 0) {
164 if (isdigit(buf[4])) {
165 for (i = 4; i < strlen(buf); i++) {
166 dump_times += temp_pow(10, (strlen(buf)
167 - i - 1))
168 * (buf[i] - '0');
169 }
170 rockchip_crtc->vop_dump_times = dump_times;
171 } else {
172 drm_modeset_lock_all(crtc->dev);
173 list_for_each_entry_safe(pos, n,
174 &rockchip_crtc->vop_dump_list_head,
175 entry) {
176 rockchip_drm_dump_plane_buffer(&pos->dump_info,
177 rockchip_crtc->frame_count);
178 }
179 drm_modeset_unlock_all(crtc->dev);
180 rockchip_crtc->frame_count++;
181 }
182 } else {
183 return -EINVAL;
184 }
185
186 return len;
187 }
188
189 static const struct file_operations rockchip_drm_dump_buffer_fops = {
190 .owner = THIS_MODULE,
191 .open = rockchip_drm_dump_buffer_open,
192 .read = seq_read,
193 .llseek = seq_lseek,
194 .release = single_release,
195 .write = rockchip_drm_dump_buffer_write,
196 };
197
rockchip_drm_add_dump_buffer(struct drm_crtc * crtc,struct dentry * root)198 int rockchip_drm_add_dump_buffer(struct drm_crtc *crtc, struct dentry *root)
199 {
200 struct dentry *vop_dump_root;
201 struct dentry *ent;
202 struct rockchip_crtc *rockchip_crtc = to_rockchip_crtc(crtc);
203
204 vop_dump_root = debugfs_create_dir("vop_dump", root);
205 rockchip_crtc->vop_dump_status = DUMP_DISABLE;
206 rockchip_crtc->vop_dump_list_init_flag = false;
207 rockchip_crtc->vop_dump_times = 0;
208 rockchip_crtc->frame_count = 0;
209 ent = debugfs_create_file("dump", 0644, vop_dump_root,
210 crtc, &rockchip_drm_dump_buffer_fops);
211 if (!ent) {
212 DRM_ERROR("create vop_plane_dump err\n");
213 debugfs_remove_recursive(vop_dump_root);
214 }
215
216 return 0;
217 }
218