drm/amd/display: Test colorop error paths

[WHAT]
Add KUnit tests for the failure paths of
amdgpu_dm_build_default_pipeline(): a failed allocation and a failed
initialization at each of the eight pipeline steps.

[HOW]
Add struct amdgpu_dm_colorop_kunit_ops, a KUnit-only indirection table
for the colorop allocation and the five DRM colorop constructors. The
constructors cannot be made to fail through their arguments, so tests
install a fake allocator and spies that delegate to the real
constructors and override the return value of one selected call.
Non-KUnit builds call the same functions directly.

Assisted-by: Copilot:Claude-Opus-5
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
This commit is contained in:
Alex Hung 2026-08-13 16:29:48 -06:00 committed by Alex Deucher
parent 4b6281959e
commit 34da32ffe3
3 changed files with 312 additions and 58 deletions

View File

@ -63,6 +63,48 @@ static const struct drm_colorop_funcs dm_colorop_funcs = {
.destroy = drm_colorop_destroy,
};
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
static struct drm_colorop *amdgpu_dm_colorop_kzalloc(void)
{
return kzalloc_obj(struct drm_colorop);
}
static const struct amdgpu_dm_colorop_kunit_ops amdgpu_dm_colorop_default_ops = {
.colorop_kzalloc_obj = amdgpu_dm_colorop_kzalloc,
.curve_1d_init = drm_plane_colorop_curve_1d_init,
.curve_1d_lut_init = drm_plane_colorop_curve_1d_lut_init,
.ctm_3x4_init = drm_plane_colorop_ctm_3x4_init,
.mult_init = drm_plane_colorop_mult_init,
.lut3d_init = drm_plane_colorop_3dlut_init,
};
static const struct amdgpu_dm_colorop_kunit_ops *amdgpu_dm_colorop_ops =
&amdgpu_dm_colorop_default_ops;
void amdgpu_dm_colorop_kunit_set_ops(const struct amdgpu_dm_colorop_kunit_ops *ops)
{
amdgpu_dm_colorop_ops = ops ? ops : &amdgpu_dm_colorop_default_ops;
}
EXPORT_IF_KUNIT(amdgpu_dm_colorop_kunit_set_ops);
#define colorop_kzalloc_obj amdgpu_dm_colorop_ops->colorop_kzalloc_obj
#define colorop_curve_1d_init amdgpu_dm_colorop_ops->curve_1d_init
#define colorop_curve_1d_lut_init amdgpu_dm_colorop_ops->curve_1d_lut_init
#define colorop_ctm_3x4_init amdgpu_dm_colorop_ops->ctm_3x4_init
#define colorop_mult_init amdgpu_dm_colorop_ops->mult_init
#define colorop_3dlut_init amdgpu_dm_colorop_ops->lut3d_init
#else
#define colorop_kzalloc_obj() kzalloc_obj(struct drm_colorop)
#define colorop_curve_1d_init drm_plane_colorop_curve_1d_init
#define colorop_curve_1d_lut_init drm_plane_colorop_curve_1d_lut_init
#define colorop_ctm_3x4_init drm_plane_colorop_ctm_3x4_init
#define colorop_mult_init drm_plane_colorop_mult_init
#define colorop_3dlut_init drm_plane_colorop_3dlut_init
#endif
STATIC_IFN_KUNIT int
amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane,
bool hw_3d_lut, struct drm_prop_enum_list *list)
@ -74,15 +116,15 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
memset(ops, 0, sizeof(ops));
/* 1D curve - DEGAM TF */
ops[i] = kzalloc_obj(*ops[0]);
ops[i] = colorop_kzalloc_obj();
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_init(dev, ops[i], plane, &dm_colorop_funcs,
amdgpu_dm_supported_degam_tfs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
ret = colorop_curve_1d_init(dev, ops[i], plane, &dm_colorop_funcs,
amdgpu_dm_supported_degam_tfs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
@ -91,14 +133,14 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
i++;
/* Multiplier */
ops[i] = kzalloc_obj(struct drm_colorop);
ops[i] = colorop_kzalloc_obj();
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_mult_init(dev, ops[i], plane, &dm_colorop_funcs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
ret = colorop_mult_init(dev, ops[i], plane, &dm_colorop_funcs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
@ -107,15 +149,14 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
i++;
/* 3x4 matrix */
ops[i] = kzalloc_obj(struct drm_colorop);
ops[i] = colorop_kzalloc_obj();
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_ctm_3x4_init(dev, ops[i], plane,
&dm_colorop_funcs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
ret = colorop_ctm_3x4_init(dev, ops[i], plane, &dm_colorop_funcs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
@ -125,15 +166,15 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
if (hw_3d_lut) {
/* 1D curve - SHAPER TF */
ops[i] = kzalloc_obj(*ops[0]);
ops[i] = colorop_kzalloc_obj();
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_init(dev, ops[i], plane, &dm_colorop_funcs,
amdgpu_dm_supported_shaper_tfs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
ret = colorop_curve_1d_init(dev, ops[i], plane, &dm_colorop_funcs,
amdgpu_dm_supported_shaper_tfs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
@ -142,17 +183,16 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
i++;
/* 1D LUT - SHAPER LUT */
ops[i] = kzalloc_obj(*ops[0]);
ops[i] = colorop_kzalloc_obj();
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_lut_init(dev, ops[i], plane,
&dm_colorop_funcs,
MAX_COLOR_LUT_ENTRIES,
DRM_COLOROP_LUT1D_INTERPOLATION_LINEAR,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
ret = colorop_curve_1d_lut_init(dev, ops[i], plane, &dm_colorop_funcs,
MAX_COLOR_LUT_ENTRIES,
DRM_COLOROP_LUT1D_INTERPOLATION_LINEAR,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
@ -161,16 +201,16 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
i++;
/* 3D LUT */
ops[i] = kzalloc_obj(*ops[0]);
ops[i] = colorop_kzalloc_obj();
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_3dlut_init(dev, ops[i], plane,
&dm_colorop_funcs, LUT3D_SIZE,
DRM_COLOROP_LUT3D_INTERPOLATION_TETRAHEDRAL,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
ret = colorop_3dlut_init(dev, ops[i], plane, &dm_colorop_funcs,
LUT3D_SIZE,
DRM_COLOROP_LUT3D_INTERPOLATION_TETRAHEDRAL,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
@ -180,15 +220,15 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
}
/* 1D curve - BLND TF */
ops[i] = kzalloc_obj(*ops[0]);
ops[i] = colorop_kzalloc_obj();
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_init(dev, ops[i], plane, &dm_colorop_funcs,
amdgpu_dm_supported_blnd_tfs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
ret = colorop_curve_1d_init(dev, ops[i], plane, &dm_colorop_funcs,
amdgpu_dm_supported_blnd_tfs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
@ -197,16 +237,16 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
i++;
/* 1D LUT - BLND LUT */
ops[i] = kzalloc_obj(struct drm_colorop);
ops[i] = colorop_kzalloc_obj();
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_lut_init(dev, ops[i], plane, &dm_colorop_funcs,
MAX_COLOR_LUT_ENTRIES,
DRM_COLOROP_LUT1D_INTERPOLATION_LINEAR,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
ret = colorop_curve_1d_lut_init(dev, ops[i], plane, &dm_colorop_funcs,
MAX_COLOR_LUT_ENTRIES,
DRM_COLOROP_LUT1D_INTERPOLATION_LINEAR,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;

View File

@ -34,8 +34,36 @@ extern const u64 amdgpu_dm_supported_blnd_tfs;
int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_prop_enum_list *list);
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
#include <drm/drm_colorop.h>
struct amdgpu_dm_colorop_kunit_ops {
struct drm_colorop *(*colorop_kzalloc_obj)(void);
int (*curve_1d_init)(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane, const struct drm_colorop_funcs *funcs,
u64 supported_tfs, uint32_t flags);
int (*curve_1d_lut_init)(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane,
const struct drm_colorop_funcs *funcs,
uint32_t lut_size,
enum drm_colorop_lut1d_interpolation_type interpolation,
uint32_t flags);
int (*ctm_3x4_init)(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane, const struct drm_colorop_funcs *funcs,
uint32_t flags);
int (*mult_init)(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane, const struct drm_colorop_funcs *funcs,
uint32_t flags);
int (*lut3d_init)(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane,
const struct drm_colorop_funcs *funcs,
uint32_t lut_size,
enum drm_colorop_lut3d_interpolation_type interpolation,
uint32_t flags);
};
int amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane,
bool hw_3d_lut, struct drm_prop_enum_list *list);
void amdgpu_dm_colorop_kunit_set_ops(const struct amdgpu_dm_colorop_kunit_ops *ops);
#endif
#endif /* __AMDGPU_DM_COLOROP_H__*/

View File

@ -136,6 +136,30 @@ static void kunit_colorop_pipeline_destroy(void *drm)
drm_colorop_pipeline_destroy((struct drm_device *)drm);
}
/*
* A plain drm_device (not an amdgpu_device) is enough: building the pipeline
* only needs the DRM mode-config infrastructure. The pipeline is destroyed
* before the DRM device so the colorops are freed while the device is valid.
*/
static struct drm_plane *dm_colorop_alloc_plane(struct kunit *test, struct drm_device **drm)
{
struct drm_plane *plane;
struct device *dev;
dev = drm_kunit_helper_alloc_device(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
*drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(**drm), 0, 0);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, *drm);
plane = drm_kunit_helper_create_primary_plane(test, *drm, NULL, NULL, NULL, 0, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane);
kunit_add_action(test, kunit_colorop_pipeline_destroy, *drm);
return plane;
}
static void dm_expect_colorop_pipeline(struct kunit *test, struct drm_device *drm,
const struct drm_prop_enum_list *list,
const enum drm_colorop_type *expected,
@ -177,34 +201,12 @@ static void dm_test_initialize_default_pipeline(struct kunit *test)
DRM_COLOROP_1D_CURVE, /* blnd TF */
DRM_COLOROP_1D_LUT, /* blnd LUT */
};
struct device *dev;
struct drm_device *drm;
struct drm_plane *plane;
struct drm_prop_enum_list list = {};
int ret;
dev = drm_kunit_helper_alloc_device(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
/*
* Allocate a plain drm_device (not an amdgpu_device) — sufficient
* because amdgpu_dm_build_default_pipeline() only needs the DRM
* mode-config infrastructure, not the amdgpu device wrapper.
*/
drm = __drm_kunit_helper_alloc_drm_device(test, dev,
sizeof(*drm), 0, 0);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
plane = drm_kunit_helper_create_primary_plane(test, drm,
NULL, NULL, NULL, 0, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane);
/*
* Destroy the pipeline before the DRM device is cleaned up so
* that the colorop objects (kzalloc'd inside the function) are
* freed while the device is still valid.
*/
kunit_add_action(test, kunit_colorop_pipeline_destroy, drm);
plane = dm_colorop_alloc_plane(test, &drm);
ret = amdgpu_dm_build_default_pipeline(drm, plane, true, &list);
KUNIT_ASSERT_EQ(test, ret, 0);
@ -308,6 +310,187 @@ static void dm_test_initialize_default_pipeline_no_3d_lut(struct kunit *test)
expected, ARRAY_SIZE(expected));
}
/* Failure paths of amdgpu_dm_build_default_pipeline() */
/*
* The DRM colorop constructors cannot be made to fail through their arguments,
* so failures are injected with a fake allocator and with spies that delegate
* to the real constructors and override the return value of one call.
*/
struct dm_colorop_fault {
int alloc_fail_at; /* allocation index returning NULL, -1 to disable */
int init_fail_at; /* init call index returning an error, -1 to disable */
int allocs;
int inits;
};
static struct dm_colorop_fault dm_colorop_fault;
static struct drm_colorop *dm_colorop_test_alloc(void)
{
if (dm_colorop_fault.allocs++ == dm_colorop_fault.alloc_fail_at)
return NULL;
return kzalloc_obj(struct drm_colorop);
}
static int dm_colorop_test_init_ret(int ret)
{
if (ret)
return ret;
if (dm_colorop_fault.inits++ == dm_colorop_fault.init_fail_at)
return -EINVAL;
return 0;
}
static int dm_colorop_test_curve_1d_init(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane,
const struct drm_colorop_funcs *funcs,
u64 supported_tfs, uint32_t flags)
{
return dm_colorop_test_init_ret(drm_plane_colorop_curve_1d_init(dev, colorop, plane,
funcs, supported_tfs,
flags));
}
static int dm_colorop_test_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane,
const struct drm_colorop_funcs *funcs,
uint32_t lut_size,
enum drm_colorop_lut1d_interpolation_type interpolation,
uint32_t flags)
{
return dm_colorop_test_init_ret(drm_plane_colorop_curve_1d_lut_init(dev, colorop, plane,
funcs, lut_size,
interpolation,
flags));
}
static int dm_colorop_test_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane,
const struct drm_colorop_funcs *funcs, uint32_t flags)
{
return dm_colorop_test_init_ret(drm_plane_colorop_ctm_3x4_init(dev, colorop, plane,
funcs, flags));
}
static int dm_colorop_test_mult_init(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane,
const struct drm_colorop_funcs *funcs, uint32_t flags)
{
return dm_colorop_test_init_ret(drm_plane_colorop_mult_init(dev, colorop, plane,
funcs, flags));
}
static int dm_colorop_test_3dlut_init(struct drm_device *dev, struct drm_colorop *colorop,
struct drm_plane *plane,
const struct drm_colorop_funcs *funcs, uint32_t lut_size,
enum drm_colorop_lut3d_interpolation_type interpolation,
uint32_t flags)
{
return dm_colorop_test_init_ret(drm_plane_colorop_3dlut_init(dev, colorop, plane, funcs,
lut_size, interpolation,
flags));
}
static const struct amdgpu_dm_colorop_kunit_ops dm_colorop_test_ops = {
.colorop_kzalloc_obj = dm_colorop_test_alloc,
.curve_1d_init = dm_colorop_test_curve_1d_init,
.curve_1d_lut_init = dm_colorop_test_1d_lut_init,
.ctm_3x4_init = dm_colorop_test_ctm_3x4_init,
.mult_init = dm_colorop_test_mult_init,
.lut3d_init = dm_colorop_test_3dlut_init,
};
static void dm_colorop_test_reset_ops(void *unused)
{
amdgpu_dm_colorop_kunit_set_ops(NULL);
}
/*
* Build the full (3D LUT) pipeline with one injected failure and return the
* error reported by amdgpu_dm_build_default_pipeline().
*/
static int dm_colorop_build_with_fault(struct kunit *test, struct drm_device **drm,
int alloc_fail_at, int init_fail_at)
{
struct drm_prop_enum_list list = {};
struct drm_plane *plane;
int ret;
plane = dm_colorop_alloc_plane(test, drm);
dm_colorop_fault = (struct dm_colorop_fault){
.alloc_fail_at = alloc_fail_at,
.init_fail_at = init_fail_at,
};
amdgpu_dm_colorop_kunit_set_ops(&dm_colorop_test_ops);
KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_colorop_test_reset_ops, NULL), 0);
ret = amdgpu_dm_build_default_pipeline(*drm, plane, true, &list);
kfree(list.name);
return ret;
}
struct dm_colorop_step_param {
const char *name;
int index;
};
/* Pipeline steps in creation order when the 3D LUT block is built. */
static const struct dm_colorop_step_param dm_colorop_step_params[] = {
{ "degam_tf", 0 },
{ "multiplier", 1 },
{ "ctm_3x4", 2 },
{ "shaper_tf", 3 },
{ "shaper_lut", 4 },
{ "lut3d", 5 },
{ "blnd_tf", 6 },
{ "blnd_lut", 7 },
};
KUNIT_ARRAY_PARAM_DESC(dm_colorop_step, dm_colorop_step_params, name);
/**
* dm_test_build_pipeline_alloc_fail() - Verify a failed alloc returns -ENOMEM and cleans up.
* @test: KUnit test context.
*/
static void dm_test_build_pipeline_alloc_fail(struct kunit *test)
{
const struct dm_colorop_step_param *param = test->param_value;
struct drm_device *drm;
int ret;
ret = dm_colorop_build_with_fault(test, &drm, param->index, -1);
KUNIT_EXPECT_EQ(test, ret, -ENOMEM);
KUNIT_EXPECT_EQ(test, dm_colorop_fault.allocs, param->index + 1);
KUNIT_EXPECT_EQ(test, dm_colorop_fault.inits, param->index);
KUNIT_EXPECT_EQ(test, drm->mode_config.num_colorop, 0);
}
/**
* dm_test_build_pipeline_init_fail() - Verify a failed init propagates the error and cleans up.
* @test: KUnit test context.
*/
static void dm_test_build_pipeline_init_fail(struct kunit *test)
{
const struct dm_colorop_step_param *param = test->param_value;
struct drm_device *drm;
int ret;
ret = dm_colorop_build_with_fault(test, &drm, -1, param->index);
KUNIT_EXPECT_EQ(test, ret, -EINVAL);
KUNIT_EXPECT_EQ(test, dm_colorop_fault.allocs, param->index + 1);
KUNIT_EXPECT_EQ(test, dm_colorop_fault.inits, param->index + 1);
KUNIT_EXPECT_EQ(test, drm->mode_config.num_colorop, 0);
}
static struct kunit_case dm_colorop_test_cases[] = {
/* degam TFs */
KUNIT_CASE(dm_test_supported_degam_tfs_has_srgb_eotf),
@ -334,6 +517,9 @@ static struct kunit_case dm_colorop_test_cases[] = {
KUNIT_CASE(dm_test_initialize_default_pipeline_dpp_3d_lut),
KUNIT_CASE(dm_test_initialize_default_pipeline_mpc_preblend),
KUNIT_CASE(dm_test_initialize_default_pipeline_no_3d_lut),
/* amdgpu_dm_build_default_pipeline failure paths */
KUNIT_CASE_PARAM(dm_test_build_pipeline_alloc_fail, dm_colorop_step_gen_params),
KUNIT_CASE_PARAM(dm_test_build_pipeline_init_fail, dm_colorop_step_gen_params),
{}
};