// SPDX-License-Identifier: GPL-2.0
#include <kunit/test.h>
#include <kunit/test-bug.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/init_syscalls.h>
/* dynamic minor (2) */
static struct miscdevice dev_dynamic_minor = {
.minor = 2 ,
.name = "dev_dynamic_minor" ,
};
/* static minor (LCD_MINOR) */
static struct miscdevice dev_static_minor = {
.minor = LCD_MINOR,
.name = "dev_static_minor" ,
};
/* misc dynamic minor */
static struct miscdevice dev_misc_dynamic_minor = {
.minor = MISC_DYNAMIC_MINOR,
.name = "dev_misc_dynamic_minor" ,
};
static void kunit_dynamic_minor(struct kunit *test)
{
int ret;
ret = misc_register(&dev_dynamic_minor);
KUNIT_EXPECT_EQ(test, 0 , ret);
KUNIT_EXPECT_EQ(test, 2 , dev_dynamic_minor.minor);
misc_deregister(&dev_dynamic_minor);
}
static void kunit_static_minor(struct kunit *test)
{
int ret;
ret = misc_register(&dev_static_minor);
KUNIT_EXPECT_EQ(test, 0 , ret);
KUNIT_EXPECT_EQ(test, LCD_MINOR, dev_static_minor.minor);
misc_deregister(&dev_static_minor);
}
static void kunit_misc_dynamic_minor(struct kunit *test)
{
int ret;
ret = misc_register(&dev_misc_dynamic_minor);
KUNIT_EXPECT_EQ(test, 0 , ret);
misc_deregister(&dev_misc_dynamic_minor);
}
struct miscdev_test_case {
const char *str;
int minor;
};
static struct miscdev_test_case miscdev_test_ranges[] = {
{
.str = "lower static range, top" ,
.minor = 15 ,
},
{
.str = "upper static range, bottom" ,
.minor = 130 ,
},
{
.str = "lower static range, bottom" ,
.minor = 0 ,
},
{
.str = "upper static range, top" ,
.minor = MISC_DYNAMIC_MINOR - 1 ,
},
};
KUNIT_ARRAY_PARAM_DESC(miscdev, miscdev_test_ranges, str);
static int miscdev_find_minors(struct kunit_suite *suite)
{
int ret;
struct miscdevice miscstat = {
.name = "miscstat" ,
};
int i;
for (i = 15 ; i >= 0 ; i--) {
miscstat.minor = i;
ret = misc_register(&miscstat);
if (ret == 0 )
break ;
}
if (ret == 0 ) {
kunit_info(suite, "found misc device minor %d available\n" ,
miscstat.minor);
miscdev_test_ranges[0 ].minor = miscstat.minor;
misc_deregister(&miscstat);
} else {
return ret;
}
for (i = 128 ; i < MISC_DYNAMIC_MINOR; i++) {
miscstat.minor = i;
ret = misc_register(&miscstat);
if (ret == 0 )
break ;
}
if (ret == 0 ) {
kunit_info(suite, "found misc device minor %d available\n" ,
miscstat.minor);
miscdev_test_ranges[1 ].minor = miscstat.minor;
misc_deregister(&miscstat);
} else {
return ret;
}
for (i = 0 ; i < miscdev_test_ranges[0 ].minor; i++) {
miscstat.minor = i;
ret = misc_register(&miscstat);
if (ret == 0 )
break ;
}
if (ret == 0 ) {
kunit_info(suite, "found misc device minor %d available\n" ,
miscstat.minor);
miscdev_test_ranges[2 ].minor = miscstat.minor;
misc_deregister(&miscstat);
} else {
return ret;
}
for (i = MISC_DYNAMIC_MINOR - 1 ; i > miscdev_test_ranges[1 ].minor; i--) {
miscstat.minor = i;
ret = misc_register(&miscstat);
if (ret == 0 )
break ;
}
if (ret == 0 ) {
kunit_info(suite, "found misc device minor %d available\n" ,
miscstat.minor);
miscdev_test_ranges[3 ].minor = miscstat.minor;
misc_deregister(&miscstat);
}
return ret;
}
static bool is_valid_dynamic_minor(int minor)
{
if (minor < 0 )
return false ;
if (minor == MISC_DYNAMIC_MINOR)
return false ;
if (minor >= 0 && minor <= 15 )
return false ;
if (minor >= 128 && minor < MISC_DYNAMIC_MINOR)
return false ;
return true ;
}
static int miscdev_test_open(struct inode *inode, struct file *file)
{
return 0 ;
}
static const struct file_operations miscdev_test_fops = {
.open = miscdev_test_open,
};
static void __init miscdev_test_can_open(struct kunit *test, struct miscdevice *misc)
{
int ret;
struct file *filp;
char *devname;
devname = kasprintf(GFP_KERNEL, "/dev/%s" , misc->name);
ret = init_mknod(devname, S_IFCHR | 0600 ,
new_encode_dev(MKDEV(MISC_MAJOR, misc->minor)));
if (ret != 0 )
KUNIT_FAIL(test, "failed to create node\n" );
filp = filp_open(devname, O_RDONLY, 0 );
if (IS_ERR_OR_NULL(filp))
KUNIT_FAIL(test, "failed to open misc device: %ld\n" , PTR_ERR(filp));
else
fput(filp);
init_unlink(devname);
kfree(devname);
}
static void __init miscdev_test_static_basic(struct kunit *test)
{
struct miscdevice misc_test = {
.name = "misc_test" ,
.fops = &miscdev_test_fops,
};
int ret;
const struct miscdev_test_case *params = test->param_value;
misc_test.minor = params->minor;
ret = misc_register(&misc_test);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_EQ(test, misc_test.minor, params->minor);
if (ret == 0 ) {
miscdev_test_can_open(test, &misc_test);
misc_deregister(&misc_test);
}
}
static void __init miscdev_test_dynamic_basic(struct kunit *test)
{
struct miscdevice misc_test = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_test" ,
.fops = &miscdev_test_fops,
};
int ret;
ret = misc_register(&misc_test);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(misc_test.minor));
if (ret == 0 ) {
miscdev_test_can_open(test, &misc_test);
misc_deregister(&misc_test);
}
}
static void miscdev_test_twice(struct kunit *test)
{
struct miscdevice misc_test = {
.name = "misc_test" ,
.fops = &miscdev_test_fops,
};
int ret;
const struct miscdev_test_case *params = test->param_value;
misc_test.minor = params->minor;
ret = misc_register(&misc_test);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_EQ(test, misc_test.minor, params->minor);
if (ret == 0 )
misc_deregister(&misc_test);
ret = misc_register(&misc_test);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_EQ(test, misc_test.minor, params->minor);
if (ret == 0 )
misc_deregister(&misc_test);
}
static void miscdev_test_duplicate_minor(struct kunit *test)
{
struct miscdevice misc1 = {
.name = "misc1" ,
.fops = &miscdev_test_fops,
};
struct miscdevice misc2 = {
.name = "misc2" ,
.fops = &miscdev_test_fops,
};
int ret;
const struct miscdev_test_case *params = test->param_value;
misc1.minor = params->minor;
misc2.minor = params->minor;
ret = misc_register(&misc1);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_EQ(test, misc1.minor, params->minor);
ret = misc_register(&misc2);
KUNIT_EXPECT_EQ(test, ret, -EBUSY);
if (ret == 0 )
misc_deregister(&misc2);
misc_deregister(&misc1);
}
static void miscdev_test_duplicate_name(struct kunit *test)
{
struct miscdevice misc1 = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc1" ,
.fops = &miscdev_test_fops,
};
struct miscdevice misc2 = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc1" ,
.fops = &miscdev_test_fops,
};
int ret;
ret = misc_register(&misc1);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(misc1.minor));
ret = misc_register(&misc2);
KUNIT_EXPECT_EQ(test, ret, -EEXIST);
if (ret == 0 )
misc_deregister(&misc2);
misc_deregister(&misc1);
}
/*
* Test that after a duplicate name failure , the reserved minor number is
* freed to be allocated next .
*/
static void miscdev_test_duplicate_name_leak(struct kunit *test)
{
struct miscdevice misc1 = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc1" ,
.fops = &miscdev_test_fops,
};
struct miscdevice misc2 = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc1" ,
.fops = &miscdev_test_fops,
};
struct miscdevice misc3 = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc3" ,
.fops = &miscdev_test_fops,
};
int ret;
int dyn_minor;
ret = misc_register(&misc1);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(misc1.minor));
/*
* Find out what is the next minor number available .
*/
ret = misc_register(&misc3);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(misc3.minor));
dyn_minor = misc3.minor;
misc_deregister(&misc3);
misc3.minor = MISC_DYNAMIC_MINOR;
ret = misc_register(&misc2);
KUNIT_EXPECT_EQ(test, ret, -EEXIST);
if (ret == 0 )
misc_deregister(&misc2);
/*
* Now check that we can still get the same minor we found before .
*/
ret = misc_register(&misc3);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(misc3.minor));
KUNIT_EXPECT_EQ(test, misc3.minor, dyn_minor);
misc_deregister(&misc3);
misc_deregister(&misc1);
}
/*
* Try to register a static minor with a duplicate name . That might not
* deallocate the minor , preventing it from being used again .
*/
static void miscdev_test_duplicate_error(struct kunit *test)
{
struct miscdevice miscdyn = {
.minor = MISC_DYNAMIC_MINOR,
.name = "name1" ,
.fops = &miscdev_test_fops,
};
struct miscdevice miscstat = {
.name = "name1" ,
.fops = &miscdev_test_fops,
};
struct miscdevice miscnew = {
.name = "name2" ,
.fops = &miscdev_test_fops,
};
int ret;
const struct miscdev_test_case *params = test->param_value;
miscstat.minor = params->minor;
miscnew.minor = params->minor;
ret = misc_register(&miscdyn);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn.minor));
ret = misc_register(&miscstat);
KUNIT_EXPECT_EQ(test, ret, -EEXIST);
if (ret == 0 )
misc_deregister(&miscstat);
ret = misc_register(&miscnew);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_EQ(test, miscnew.minor, params->minor);
if (ret == 0 )
misc_deregister(&miscnew);
misc_deregister(&miscdyn);
}
static void __init miscdev_test_dynamic_only_range(struct kunit *test)
{
int ret;
struct miscdevice *miscdev;
const int dynamic_minors = 256 ;
int i;
miscdev = kunit_kmalloc_array(test, dynamic_minors,
sizeof (struct miscdevice),
GFP_KERNEL | __GFP_ZERO);
for (i = 0 ; i < dynamic_minors; i++) {
miscdev[i].minor = MISC_DYNAMIC_MINOR;
miscdev[i].name = kasprintf(GFP_KERNEL, "misc_test%d" , i);
miscdev[i].fops = &miscdev_test_fops;
ret = misc_register(&miscdev[i]);
if (ret != 0 )
break ;
/*
* This is the bug we are looking for !
* We asked for a dynamic minor and got a minor in the static range space .
*/
if (miscdev[i].minor >= 0 && miscdev[i].minor <= 15 ) {
KUNIT_FAIL(test, "misc_register allocated minor %d\n" , miscdev[i].minor);
i++;
break ;
}
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdev[i].minor));
}
for (i--; i >= 0 ; i--) {
miscdev_test_can_open(test, &miscdev[i]);
misc_deregister(&miscdev[i]);
kfree_const(miscdev[i].name);
}
KUNIT_EXPECT_EQ(test, ret, 0 );
}
static void __init miscdev_test_collision(struct kunit *test)
{
int ret;
struct miscdevice *miscdev;
struct miscdevice miscstat = {
.name = "miscstat" ,
.fops = &miscdev_test_fops,
};
const int dynamic_minors = 256 ;
int i;
miscdev = kunit_kmalloc_array(test, dynamic_minors,
sizeof (struct miscdevice),
GFP_KERNEL | __GFP_ZERO);
miscstat.minor = miscdev_test_ranges[0 ].minor;
ret = misc_register(&miscstat);
KUNIT_ASSERT_EQ(test, ret, 0 );
KUNIT_EXPECT_EQ(test, miscstat.minor, miscdev_test_ranges[0 ].minor);
for (i = 0 ; i < dynamic_minors; i++) {
miscdev[i].minor = MISC_DYNAMIC_MINOR;
miscdev[i].name = kasprintf(GFP_KERNEL, "misc_test%d" , i);
miscdev[i].fops = &miscdev_test_fops;
ret = misc_register(&miscdev[i]);
if (ret != 0 )
break ;
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdev[i].minor));
}
for (i--; i >= 0 ; i--) {
miscdev_test_can_open(test, &miscdev[i]);
misc_deregister(&miscdev[i]);
kfree_const(miscdev[i].name);
}
misc_deregister(&miscstat);
KUNIT_EXPECT_EQ(test, ret, 0 );
}
static void __init miscdev_test_collision_reverse(struct kunit *test)
{
int ret;
struct miscdevice *miscdev;
struct miscdevice miscstat = {
.name = "miscstat" ,
.fops = &miscdev_test_fops,
};
const int dynamic_minors = 256 ;
int i;
miscdev = kunit_kmalloc_array(test, dynamic_minors,
sizeof (struct miscdevice),
GFP_KERNEL | __GFP_ZERO);
for (i = 0 ; i < dynamic_minors; i++) {
miscdev[i].minor = MISC_DYNAMIC_MINOR;
miscdev[i].name = kasprintf(GFP_KERNEL, "misc_test%d" , i);
miscdev[i].fops = &miscdev_test_fops;
ret = misc_register(&miscdev[i]);
if (ret != 0 )
break ;
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdev[i].minor));
}
KUNIT_EXPECT_EQ(test, ret, 0 );
miscstat.minor = miscdev_test_ranges[0 ].minor;
ret = misc_register(&miscstat);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_EQ(test, miscstat.minor, miscdev_test_ranges[0 ].minor);
if (ret == 0 )
misc_deregister(&miscstat);
for (i--; i >= 0 ; i--) {
miscdev_test_can_open(test, &miscdev[i]);
misc_deregister(&miscdev[i]);
kfree_const(miscdev[i].name);
}
}
static void __init miscdev_test_conflict(struct kunit *test)
{
int ret;
struct miscdevice miscdyn = {
.name = "miscdyn" ,
.minor = MISC_DYNAMIC_MINOR,
.fops = &miscdev_test_fops,
};
struct miscdevice miscstat = {
.name = "miscstat" ,
.fops = &miscdev_test_fops,
};
ret = misc_register(&miscdyn);
KUNIT_ASSERT_EQ(test, ret, 0 );
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn.minor));
/*
* Try to register a static minor with the same minor as the
* dynamic one .
*/
miscstat.minor = miscdyn.minor;
ret = misc_register(&miscstat);
KUNIT_EXPECT_EQ(test, ret, -EBUSY);
if (ret == 0 )
misc_deregister(&miscstat);
miscdev_test_can_open(test, &miscdyn);
misc_deregister(&miscdyn);
}
static void __init miscdev_test_conflict_reverse(struct kunit *test)
{
int ret;
struct miscdevice miscdyn = {
.name = "miscdyn" ,
.minor = MISC_DYNAMIC_MINOR,
.fops = &miscdev_test_fops,
};
struct miscdevice miscstat = {
.name = "miscstat" ,
.fops = &miscdev_test_fops,
};
/*
* Find the first available dynamic minor to use it as a static
* minor later on .
*/
ret = misc_register(&miscdyn);
KUNIT_ASSERT_EQ(test, ret, 0 );
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn.minor));
miscstat.minor = miscdyn.minor;
misc_deregister(&miscdyn);
ret = misc_register(&miscstat);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_EQ(test, miscstat.minor, miscdyn.minor);
/*
* Try to register a dynamic minor after registering a static minor
* within the dynamic range . It should work but get a different
* minor .
*/
miscdyn.minor = MISC_DYNAMIC_MINOR;
ret = misc_register(&miscdyn);
KUNIT_EXPECT_EQ(test, ret, 0 );
KUNIT_EXPECT_NE(test, miscdyn.minor, miscstat.minor);
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn.minor));
if (ret == 0 )
misc_deregister(&miscdyn);
miscdev_test_can_open(test, &miscstat);
misc_deregister(&miscstat);
}
static struct kunit_case test_cases[] = {
KUNIT_CASE(kunit_dynamic_minor),
KUNIT_CASE(kunit_static_minor),
KUNIT_CASE(kunit_misc_dynamic_minor),
KUNIT_CASE_PARAM(miscdev_test_twice, miscdev_gen_params),
KUNIT_CASE_PARAM(miscdev_test_duplicate_minor, miscdev_gen_params),
KUNIT_CASE(miscdev_test_duplicate_name),
KUNIT_CASE(miscdev_test_duplicate_name_leak),
KUNIT_CASE_PARAM(miscdev_test_duplicate_error, miscdev_gen_params),
{}
};
static struct kunit_suite test_suite = {
.name = "miscdev" ,
.suite_init = miscdev_find_minors,
.test_cases = test_cases,
};
kunit_test_suite(test_suite);
static struct kunit_case __refdata test_init_cases[] = {
KUNIT_CASE_PARAM(miscdev_test_static_basic, miscdev_gen_params),
KUNIT_CASE(miscdev_test_dynamic_basic),
KUNIT_CASE(miscdev_test_dynamic_only_range),
KUNIT_CASE(miscdev_test_collision),
KUNIT_CASE(miscdev_test_collision_reverse),
KUNIT_CASE(miscdev_test_conflict),
KUNIT_CASE(miscdev_test_conflict_reverse),
{}
};
static struct kunit_suite test_init_suite = {
.name = "miscdev_init" ,
.suite_init = miscdev_find_minors,
.test_cases = test_init_cases,
};
kunit_test_init_section_suite(test_init_suite);
MODULE_LICENSE("GPL" );
MODULE_AUTHOR("Vimal Agrawal" );
MODULE_AUTHOR("Thadeu Lima de Souza Cascardo <cascardo@igalia.com>" );
MODULE_DESCRIPTION("Test module for misc character devices" );
Messung V0.5 in Prozent C=99 H=91 G=94
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland