| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) STMicroelectronics SA 2015 |
| * Authors: Yannick Fertre <yannick.fertre@st.com> |
| * Hugues Fruchet <hugues.fruchet@st.com> |
| */ |
| |
| #include <linux/debugfs.h> |
| |
| #include "hva.h" |
| #include "hva-hw.h" |
| |
| static void format_ctx(struct seq_file *s, struct hva_ctx *ctx) |
| { |
| struct hva_streaminfo *stream = &ctx->streaminfo; |
| struct hva_frameinfo *frame = &ctx->frameinfo; |
| struct hva_controls *ctrls = &ctx->ctrls; |
| struct hva_ctx_dbg *dbg = &ctx->dbg; |
| u32 bitrate_mode, aspect, entropy, vui_sar, sei_fp; |
| |
| seq_printf(s, "|-%s\n |\n", ctx->name); |
| |
| seq_printf(s, " |-[%sframe info]\n", |
| ctx->flags & HVA_FLAG_FRAMEINFO ? "" : "default "); |
| seq_printf(s, " | |- pixel format=%4.4s\n" |
| " | |- wxh=%dx%d\n" |
| " | |- wxh (w/ encoder alignment constraint)=%dx%d\n" |
| " |\n", |
| (char *)&frame->pixelformat, |
| frame->width, frame->height, |
| frame->aligned_width, frame->aligned_height); |
| |
| seq_printf(s, " |-[%sstream info]\n", |
| ctx->flags & HVA_FLAG_STREAMINFO ? "" : "default "); |
| seq_printf(s, " | |- stream format=%4.4s\n" |
| " | |- wxh=%dx%d\n" |
| " | |- %s\n" |
| " | |- %s\n" |
| " |\n", |
| (char *)&stream->streamformat, |
| stream->width, stream->height, |
| stream->profile, stream->level); |
| |
| bitrate_mode = V4L2_CID_MPEG_VIDEO_BITRATE_MODE; |
| aspect = V4L2_CID_MPEG_VIDEO_ASPECT; |
| seq_puts(s, " |-[parameters]\n"); |
| seq_printf(s, " | |- %s\n" |
| " | |- bitrate=%d bps\n" |
| " | |- GOP size=%d\n" |
| " | |- video aspect=%s\n" |
| " | |- framerate=%d/%d\n", |
| v4l2_ctrl_get_menu(bitrate_mode)[ctrls->bitrate_mode], |
| ctrls->bitrate, |
| ctrls->gop_size, |
| v4l2_ctrl_get_menu(aspect)[ctrls->aspect], |
| ctrls->time_per_frame.denominator, |
| ctrls->time_per_frame.numerator); |
| |
| entropy = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE; |
| vui_sar = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC; |
| sei_fp = V4L2_CID_MPEG_VIDEO_H264_SEI_FP_ARRANGEMENT_TYPE; |
| if (stream->streamformat == V4L2_PIX_FMT_H264) { |
| seq_printf(s, " | |- %s entropy mode\n" |
| " | |- CPB size=%d kB\n" |
| " | |- DCT8x8 enable=%s\n" |
| " | |- qpmin=%d\n" |
| " | |- qpmax=%d\n" |
| " | |- PAR enable=%s\n" |
| " | |- PAR id=%s\n" |
| " | |- SEI frame packing enable=%s\n" |
| " | |- SEI frame packing type=%s\n", |
| v4l2_ctrl_get_menu(entropy)[ctrls->entropy_mode], |
| ctrls->cpb_size, |
| ctrls->dct8x8 ? "true" : "false", |
| ctrls->qpmin, |
| ctrls->qpmax, |
| ctrls->vui_sar ? "true" : "false", |
| v4l2_ctrl_get_menu(vui_sar)[ctrls->vui_sar_idc], |
| ctrls->sei_fp ? "true" : "false", |
| v4l2_ctrl_get_menu(sei_fp)[ctrls->sei_fp_type]); |
| } |
| |
| if (ctx->sys_errors || ctx->encode_errors || ctx->frame_errors) { |
| seq_puts(s, " |\n |-[errors]\n"); |
| seq_printf(s, " | |- system=%d\n" |
| " | |- encoding=%d\n" |
| " | |- frame=%d\n", |
| ctx->sys_errors, |
| ctx->encode_errors, |
| ctx->frame_errors); |
| } |
| |
| seq_puts(s, " |\n |-[performances]\n"); |
| seq_printf(s, " | |- frames encoded=%d\n" |
| " | |- avg HW processing duration (0.1ms)=%d [min=%d, max=%d]\n" |
| " | |- avg encoding period (0.1ms)=%d [min=%d, max=%d]\n" |
| " | |- avg fps (0.1Hz)=%d\n" |
| " | |- max reachable fps (0.1Hz)=%d\n" |
| " | |- avg bitrate (kbps)=%d [min=%d, max=%d]\n" |
| " | |- last bitrate (kbps)=%d\n", |
| dbg->cnt_duration, |
| dbg->avg_duration, |
| dbg->min_duration, |
| dbg->max_duration, |
| dbg->avg_period, |
| dbg->min_period, |
| dbg->max_period, |
| dbg->avg_fps, |
| dbg->max_fps, |
| dbg->avg_bitrate, |
| dbg->min_bitrate, |
| dbg->max_bitrate, |
| dbg->last_bitrate); |
| } |
| |
| /* |
| * performance debug info |
| */ |
| void hva_dbg_perf_begin(struct hva_ctx *ctx) |
| { |
| u64 div; |
| u32 period; |
| u32 bitrate; |
| struct hva_ctx_dbg *dbg = &ctx->dbg; |
| ktime_t prev = dbg->begin; |
| |
| dbg->begin = ktime_get(); |
| |
| if (dbg->is_valid_period) { |
| /* encoding period */ |
| div = (u64)ktime_us_delta(dbg->begin, prev); |
| do_div(div, 100); |
| period = (u32)div; |
| dbg->min_period = min(period, dbg->min_period); |
| dbg->max_period = max(period, dbg->max_period); |
| dbg->total_period += period; |
| dbg->cnt_period++; |
| |
| /* |
| * minimum and maximum bitrates are based on the |
| * encoding period values upon a window of 32 samples |
| */ |
| dbg->window_duration += period; |
| dbg->cnt_window++; |
| if (dbg->cnt_window >= 32) { |
| /* |
| * bitrate in kbps = (size * 8 / 1000) / |
| * (duration / 10000) |
| * = size * 80 / duration |
| */ |
| if (dbg->window_duration > 0) { |
| div = (u64)dbg->window_stream_size * 80; |
| do_div(div, dbg->window_duration); |
| bitrate = (u32)div; |
| dbg->last_bitrate = bitrate; |
| dbg->min_bitrate = min(bitrate, |
| dbg->min_bitrate); |
| dbg->max_bitrate = max(bitrate, |
| dbg->max_bitrate); |
| } |
| dbg->window_stream_size = 0; |
| dbg->window_duration = 0; |
| dbg->cnt_window = 0; |
| } |
| } |
| |
| /* |
| * filter sequences valid for performance: |
| * - begin/begin (no stream available) is an invalid sequence |
| * - begin/end is a valid sequence |
| */ |
| dbg->is_valid_period = false; |
| } |
| |
| void hva_dbg_perf_end(struct hva_ctx *ctx, struct hva_stream *stream) |
| { |
| struct device *dev = ctx_to_dev(ctx); |
| u64 div; |
| u32 duration; |
| u32 bytesused; |
| u32 timestamp; |
| struct hva_ctx_dbg *dbg = &ctx->dbg; |
| ktime_t end = ktime_get(); |
| |
| /* stream bytesused and timestamp in us */ |
| bytesused = vb2_get_plane_payload(&stream->vbuf.vb2_buf, 0); |
| div = stream->vbuf.vb2_buf.timestamp; |
| do_div(div, 1000); |
| timestamp = (u32)div; |
| |
| /* encoding duration */ |
| div = (u64)ktime_us_delta(end, dbg->begin); |
| |
| dev_dbg(dev, |
| "%s perf stream[%d] dts=%d encoded using %d bytes in %d us", |
| ctx->name, |
| stream->vbuf.sequence, |
| timestamp, |
| bytesused, (u32)div); |
| |
| do_div(div, 100); |
| duration = (u32)div; |
| |
| dbg->min_duration = min(duration, dbg->min_duration); |
| dbg->max_duration = max(duration, dbg->max_duration); |
| dbg->total_duration += duration; |
| dbg->cnt_duration++; |
| |
| /* |
| * the average bitrate is based on the total stream size |
| * and the total encoding periods |
| */ |
| dbg->total_stream_size += bytesused; |
| dbg->window_stream_size += bytesused; |
| |
| dbg->is_valid_period = true; |
| } |
| |
| static void hva_dbg_perf_compute(struct hva_ctx *ctx) |
| { |
| u64 div; |
| struct hva_ctx_dbg *dbg = &ctx->dbg; |
| |
| if (dbg->cnt_duration > 0) { |
| div = (u64)dbg->total_duration; |
| do_div(div, dbg->cnt_duration); |
| dbg->avg_duration = (u32)div; |
| } else { |
| dbg->avg_duration = 0; |
| } |
| |
| if (dbg->total_duration > 0) { |
| div = (u64)dbg->cnt_duration * 100000; |
| do_div(div, dbg->total_duration); |
| dbg->max_fps = (u32)div; |
| } else { |
| dbg->max_fps = 0; |
| } |
| |
| if (dbg->cnt_period > 0) { |
| div = (u64)dbg->total_period; |
| do_div(div, dbg->cnt_period); |
| dbg->avg_period = (u32)div; |
| } else { |
| dbg->avg_period = 0; |
| } |
| |
| if (dbg->total_period > 0) { |
| div = (u64)dbg->cnt_period * 100000; |
| do_div(div, dbg->total_period); |
| dbg->avg_fps = (u32)div; |
| } else { |
| dbg->avg_fps = 0; |
| } |
| |
| if (dbg->total_period > 0) { |
| /* |
| * bitrate in kbps = (video size * 8 / 1000) / |
| * (video duration / 10000) |
| * = video size * 80 / video duration |
| */ |
| div = (u64)dbg->total_stream_size * 80; |
| do_div(div, dbg->total_period); |
| dbg->avg_bitrate = (u32)div; |
| } else { |
| dbg->avg_bitrate = 0; |
| } |
| } |
| |
| /* |
| * device debug info |
| */ |
| |
| static int device_show(struct seq_file *s, void *data) |
| { |
| struct hva_dev *hva = s->private; |
| |
| seq_printf(s, "[%s]\n", hva->v4l2_dev.name); |
| seq_printf(s, "registered as /dev/video%d\n", hva->vdev->num); |
| |
| return 0; |
| } |
| |
| static int encoders_show(struct seq_file *s, void *data) |
| { |
| struct hva_dev *hva = s->private; |
| unsigned int i = 0; |
| |
| seq_printf(s, "[encoders]\n|- %d registered encoders:\n", |
| hva->nb_of_encoders); |
| |
| while (hva->encoders[i]) { |
| seq_printf(s, "|- %s: %4.4s => %4.4s\n", hva->encoders[i]->name, |
| (char *)&hva->encoders[i]->pixelformat, |
| (char *)&hva->encoders[i]->streamformat); |
| i++; |
| } |
| |
| return 0; |
| } |
| |
| static int last_show(struct seq_file *s, void *data) |
| { |
| struct hva_dev *hva = s->private; |
| struct hva_ctx *last_ctx = &hva->dbg.last_ctx; |
| |
| if (last_ctx->flags & HVA_FLAG_STREAMINFO) { |
| seq_puts(s, "[last encoding]\n"); |
| |
| hva_dbg_perf_compute(last_ctx); |
| format_ctx(s, last_ctx); |
| } else { |
| seq_puts(s, "[no information recorded about last encoding]\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int regs_show(struct seq_file *s, void *data) |
| { |
| struct hva_dev *hva = s->private; |
| |
| hva_hw_dump_regs(hva, s); |
| |
| return 0; |
| } |
| |
| #define hva_dbg_create_entry(name) \ |
| debugfs_create_file(#name, 0444, hva->dbg.debugfs_entry, hva, \ |
| &name##_fops) |
| |
| DEFINE_SHOW_ATTRIBUTE(device); |
| DEFINE_SHOW_ATTRIBUTE(encoders); |
| DEFINE_SHOW_ATTRIBUTE(last); |
| DEFINE_SHOW_ATTRIBUTE(regs); |
| |
| void hva_debugfs_create(struct hva_dev *hva) |
| { |
| hva->dbg.debugfs_entry = debugfs_create_dir(HVA_NAME, NULL); |
| |
| hva_dbg_create_entry(device); |
| hva_dbg_create_entry(encoders); |
| hva_dbg_create_entry(last); |
| hva_dbg_create_entry(regs); |
| } |
| |
| void hva_debugfs_remove(struct hva_dev *hva) |
| { |
| debugfs_remove_recursive(hva->dbg.debugfs_entry); |
| hva->dbg.debugfs_entry = NULL; |
| } |
| |
| /* |
| * context (instance) debug info |
| */ |
| |
| static int ctx_show(struct seq_file *s, void *data) |
| { |
| struct hva_ctx *ctx = s->private; |
| |
| seq_printf(s, "[running encoding %d]\n", ctx->id); |
| |
| hva_dbg_perf_compute(ctx); |
| format_ctx(s, ctx); |
| |
| return 0; |
| } |
| |
| DEFINE_SHOW_ATTRIBUTE(ctx); |
| |
| void hva_dbg_ctx_create(struct hva_ctx *ctx) |
| { |
| struct hva_dev *hva = ctx->hva_dev; |
| char name[4] = ""; |
| |
| ctx->dbg.min_duration = UINT_MAX; |
| ctx->dbg.min_period = UINT_MAX; |
| ctx->dbg.min_bitrate = UINT_MAX; |
| |
| snprintf(name, sizeof(name), "%d", hva->instance_id); |
| |
| ctx->dbg.debugfs_entry = debugfs_create_file(name, 0444, |
| hva->dbg.debugfs_entry, |
| ctx, &ctx_fops); |
| } |
| |
| void hva_dbg_ctx_remove(struct hva_ctx *ctx) |
| { |
| struct hva_dev *hva = ctx->hva_dev; |
| |
| if (ctx->flags & HVA_FLAG_STREAMINFO) |
| /* save context before removing */ |
| memcpy(&hva->dbg.last_ctx, ctx, sizeof(*ctx)); |
| |
| debugfs_remove(ctx->dbg.debugfs_entry); |
| } |