From nobody Fri Dec 19 17:25:29 2025 Received: from fanzine2.igalia.com (fanzine.igalia.com [178.60.130.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5998A2701AA for ; Wed, 23 Apr 2025 08:45:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=178.60.130.6 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745397964; cv=none; b=nmEYnm4LrtGB8O2qcqkFSHUlVLFhBnjjYTjPDM5/Ln4pZZ69OuxZvcWxrHYlQyVMJODnb8YjBSm8/t4B2wMjKa4wqZUi+SaiUgNvhbm93hQz3UvNmLqxzCvFeGJ/VhgoB9x+L1SpaOVztGBkXPDwfKC0b0fW3Cv+S2g/It6MQNE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745397964; c=relaxed/simple; bh=puQWqXKZUZWydEqrwSNmh1bgOWVgNZ7m05mFyOV4z8c=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=KKk8WHquJ1j8mCUFa4h83sYhLkpVB4agCtgMY3xc50bE6RUmaBinGP9/YX1pyHtvXKQTK2+9XPEMYA4ZTkC7Dc1TOok7WbyiojBZvC2f8MzOINOndwa/c1MRM5rR/gcVR+0Q5Ji5oiPVB5z0o+JsbUqb+R1vsXWw0vS3+DO4nxc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=BJpCchQw; arc=none smtp.client-ip=178.60.130.6 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="BJpCchQw" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=8PPMTqFnHWaluWgAu7vm2qVJC0SfqKqu9UInCp27HcE=; b=BJpCchQwvel32bY+xxPvuedXqS iSo8n3A1SU4GS3wnEBmlIu+LoYAepgUTaqO/H0rTRaCNVdUZEhQ55k+n4z8gIgHjQ5nbiFmuUaAqD FzTUxvjvspVFNsxta3qG9os+azJqhZQcyXziQRWIuDZfH+PVhPQtgHAeuudc/+4l2cvXbgC+Se09O eAgtJpLtntGtTY/WHjYIPYxz1Fcg1FvhLXLXIp2hyfUCr0fpneOyzXgI8ikIQh3+TITuI9zZxNuDP sYhw43E7xgznGGFqLFeqkrziBrG4GCyBH5kaXRALZ9XB7vOrrmSbR74Ax+2ikLzPAnjVxdE8+mNA2 UWUjUINw==; Received: from 179-125-92-204-dinamico.pombonet.net.br ([179.125.92.204] helo=[192.168.67.187]) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1u7Vju-006pQt-Kg; Wed, 23 Apr 2025 10:45:54 +0200 From: Thadeu Lima de Souza Cascardo Date: Wed, 23 Apr 2025 05:45:42 -0300 Subject: [PATCH v4 1/2] char: misc: restrict the dynamic range to exclude reserved minors Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250423-misc-dynrange-v4-1-133b5ae4ca18@igalia.com> References: <20250423-misc-dynrange-v4-0-133b5ae4ca18@igalia.com> In-Reply-To: <20250423-misc-dynrange-v4-0-133b5ae4ca18@igalia.com> To: Arnd Bergmann , Greg Kroah-Hartman Cc: Andrew Morton , Dirk VanDerMerwe , Vimal Agrawal , linux-kernel@vger.kernel.org, Thadeu Lima de Souza Cascardo , kernel-dev@igalia.com X-Mailer: b4 0.14.2 When this was first reported [1], the possibility of having sufficient number of dynamic misc devices was theoretical, in the case of dlm driver. In practice, its userspace never created more than one device. What we know from commit ab760791c0cf ("char: misc: Increase the maximum number of dynamic misc devices to 1048448"), is that the miscdevice interface has been used for allocating more than the single-shot devices it was designed for. And it is not only coresight_tmc, but many other drivers are able to create multiple devices. On systems like the ones described in the above commit, it is certain that the dynamic allocation will allocate certain reserved minor numbers, leading to failures when a later driver tries to claim its reserved number. Instead of excluding the historically statically allocated range from dynamic allocation, restrict the latter to minors above 255. That also removes the need for DYNAMIC_MINORS and the convolution in allocating minor numbers, simplifying the code. Since commit ab760791c0cf ("char: misc: Increase the maximum number of dynamic misc devices to 1048448") has been applied, such range is already possible. And given such devices already need to be dynamically created, there should be no systems where this might become a problem. [1] https://lore.kernel.org/all/1257813017-28598-3-git-send-email-cascardo@= holoscopio.com/ Signed-off-by: Thadeu Lima de Souza Cascardo --- drivers/char/misc.c | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/drivers/char/misc.c b/drivers/char/misc.c index dda466f9181acf76564b5e41ed6e858928e56182..d5accc10a110098f7090dd0f900= bc5fae5f75f74 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -58,9 +58,8 @@ static LIST_HEAD(misc_list); static DEFINE_MUTEX(misc_mtx); =20 /* - * Assigned numbers, used for dynamic minors + * Assigned numbers. */ -#define DYNAMIC_MINORS 128 /* like dynamic majors */ static DEFINE_IDA(misc_minors_ida); =20 static int misc_minor_alloc(int minor) @@ -69,34 +68,17 @@ static int misc_minor_alloc(int minor) =20 if (minor =3D=3D MISC_DYNAMIC_MINOR) { /* allocate free id */ - ret =3D ida_alloc_max(&misc_minors_ida, DYNAMIC_MINORS - 1, GFP_KERNEL); - if (ret >=3D 0) { - ret =3D DYNAMIC_MINORS - ret - 1; - } else { - ret =3D ida_alloc_range(&misc_minors_ida, MISC_DYNAMIC_MINOR + 1, - MINORMASK, GFP_KERNEL); - } + ret =3D ida_alloc_range(&misc_minors_ida, MISC_DYNAMIC_MINOR + 1, + MINORMASK, GFP_KERNEL); } else { - /* specific minor, check if it is in dynamic or misc dynamic range */ - if (minor < DYNAMIC_MINORS) { - minor =3D DYNAMIC_MINORS - minor - 1; - ret =3D ida_alloc_range(&misc_minors_ida, minor, minor, GFP_KERNEL); - } else if (minor > MISC_DYNAMIC_MINOR) { - ret =3D ida_alloc_range(&misc_minors_ida, minor, minor, GFP_KERNEL); - } else { - /* case of non-dynamic minors, no need to allocate id */ - ret =3D 0; - } + ret =3D ida_alloc_range(&misc_minors_ida, minor, minor, GFP_KERNEL); } return ret; } =20 static void misc_minor_free(int minor) { - if (minor < DYNAMIC_MINORS) - ida_free(&misc_minors_ida, DYNAMIC_MINORS - minor - 1); - else if (minor > MISC_DYNAMIC_MINOR) - ida_free(&misc_minors_ida, minor); + ida_free(&misc_minors_ida, minor); } =20 #ifdef CONFIG_PROC_FS --=20 2.47.2 From nobody Fri Dec 19 17:25:29 2025 Received: from fanzine2.igalia.com (fanzine.igalia.com [178.60.130.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 121561D5ADE for ; Wed, 23 Apr 2025 08:46:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=178.60.130.6 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745397965; cv=none; b=BXc2/kt4/N6U9MMA0nZfXaimNtCGw4sLA0gFBDbkUpb3RQuWyGFCfmwgCh+AQOCBYWh1yasuCqmPZt5IHEGSwd3KI/qyZ9cS8RnLbY+gBfkeYagzmPzhRd6dGzdKp2p1QfI6/7rUjrzTjPWN/MAZXUJyZqTAjYp/pdxf1NAnTO0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745397965; c=relaxed/simple; bh=xALn8Qlh5CO8ogxvIuLu/MLrJFNPCd6tz3z83Sb1ecw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=SKHXGQTxaj7JbAuAOgOEJ5jTsPYOrjdutVEvsJtmvD6lF1xBvksXNSMInsy1TqVWQCBgkfDWjgN62lBJzGW8KFEKKjA4AdrhLTt5wt2Mm5cNZvLwQmdNZIPkc7gbPxKnbyhj3Y2lJQy87qA/N8mUmiEi6WovyXMuqWBLnWVtNGY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=gbOubNSS; arc=none smtp.client-ip=178.60.130.6 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="gbOubNSS" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=uSOHHhoP1vSi/c6pYCpo7YAENT8OAZgRhucRe/f++/k=; b=gbOubNSSTXjbYVW+L0IXoV3uyx D2WhrBPrBs3T53CzxDosSt700xX5gY+xZhAgLpG5BVxw3P2rGid4AVjpdvAw2PiJKTjyj+5ipLEkB 2B6wADGKIu7GUMcarIIE0J9YaKPZanW/QmZkqRXr0OHt4BpbHjwxcv+syHIpclRku0yKUoJ/8z4xw ZICQ6g5ha6UIkv07Al89Fwjf27kX68vZfuV3//D9cvu68glxXvfzew8C/jk/11xmBJZIc9iJskGhr pcwclJTrmqJZD/MTUaoc3tUEK0JWsSgbD+0DKUQxOgcpVuu46F6MRHSbZS/+qiv7AcwNXWSmO7KIt S+gkKuZg==; Received: from 179-125-92-204-dinamico.pombonet.net.br ([179.125.92.204] helo=[192.168.67.187]) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1u7Vjx-006pQt-Vw; Wed, 23 Apr 2025 10:45:58 +0200 From: Thadeu Lima de Souza Cascardo Date: Wed, 23 Apr 2025 05:45:43 -0300 Subject: [PATCH v4 2/2] char: misc: add test cases Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250423-misc-dynrange-v4-2-133b5ae4ca18@igalia.com> References: <20250423-misc-dynrange-v4-0-133b5ae4ca18@igalia.com> In-Reply-To: <20250423-misc-dynrange-v4-0-133b5ae4ca18@igalia.com> To: Arnd Bergmann , Greg Kroah-Hartman Cc: Andrew Morton , Dirk VanDerMerwe , Vimal Agrawal , linux-kernel@vger.kernel.org, Thadeu Lima de Souza Cascardo , kernel-dev@igalia.com X-Mailer: b4 0.14.2 Add test cases for static and dynamic minor number allocation and deallocation. While at it, improve description and test suite name. Some of the cases include: - that static and dynamic allocation reserved the expected minors. - that registering duplicate minors or duplicate names will fail. - that failing to create a sysfs file (due to duplicate names) will deallocate the dynamic minor correctly. - that dynamic allocation does not allocate a minor number in the static range. - that there are no collisions when mixing dynamic and static allocations. - that opening devices with various minor device numbers work. - that registering a static number in the dynamic range won't conflict with a dynamic allocation. This last test verifies the bug fixed by commit 6d04d2b554b1 ("misc: misc_minor_alloc to use ida for all dynamic/misc dynamic minors") has not regressed. Signed-off-by: Thadeu Lima de Souza Cascardo --- drivers/misc/misc_minor_kunit.c | 589 ++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 587 insertions(+), 2 deletions(-) diff --git a/drivers/misc/misc_minor_kunit.c b/drivers/misc/misc_minor_kuni= t.c index 293e0fb7e43edc330842722e1132d16cd23e3aa8..30eceac5f1b6402b0f918af6f56= 602ed1a6c14ec 100644 --- a/drivers/misc/misc_minor_kunit.c +++ b/drivers/misc/misc_minor_kunit.c @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include =20 /* dynamic minor (2) */ static struct miscdevice dev_dynamic_minor =3D { @@ -51,19 +54,601 @@ static void kunit_misc_dynamic_minor(struct kunit *tes= t) misc_deregister(&dev_misc_dynamic_minor); } =20 +struct miscdev_test_case { + const char *str; + int minor; +}; + +static struct miscdev_test_case miscdev_test_ranges[] =3D { + { + .str =3D "lower static range, top", + .minor =3D 15, + }, + { + .str =3D "upper static range, bottom", + .minor =3D 130, + }, + { + .str =3D "lower static range, bottom", + .minor =3D 0, + }, + { + .str =3D "upper static range, top", + .minor =3D 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 =3D { + .name =3D "miscstat", + }; + int i; + + for (i =3D 15; i >=3D 0; i--) { + miscstat.minor =3D i; + ret =3D misc_register(&miscstat); + if (ret =3D=3D 0) + break; + } + + if (ret =3D=3D 0) { + kunit_info(suite, "found misc device minor %d available\n", + miscstat.minor); + miscdev_test_ranges[0].minor =3D miscstat.minor; + misc_deregister(&miscstat); + } else { + return ret; + } + + for (i =3D 128; i < MISC_DYNAMIC_MINOR; i++) { + miscstat.minor =3D i; + ret =3D misc_register(&miscstat); + if (ret =3D=3D 0) + break; + } + + if (ret =3D=3D 0) { + kunit_info(suite, "found misc device minor %d available\n", + miscstat.minor); + miscdev_test_ranges[1].minor =3D miscstat.minor; + misc_deregister(&miscstat); + } else { + return ret; + } + + for (i =3D 0; i < miscdev_test_ranges[0].minor; i++) { + miscstat.minor =3D i; + ret =3D misc_register(&miscstat); + if (ret =3D=3D 0) + break; + } + + if (ret =3D=3D 0) { + kunit_info(suite, "found misc device minor %d available\n", + miscstat.minor); + miscdev_test_ranges[2].minor =3D miscstat.minor; + misc_deregister(&miscstat); + } else { + return ret; + } + + for (i =3D MISC_DYNAMIC_MINOR - 1; i > miscdev_test_ranges[1].minor; i--)= { + miscstat.minor =3D i; + ret =3D misc_register(&miscstat); + if (ret =3D=3D 0) + break; + } + + if (ret =3D=3D 0) { + kunit_info(suite, "found misc device minor %d available\n", + miscstat.minor); + miscdev_test_ranges[3].minor =3D miscstat.minor; + misc_deregister(&miscstat); + } + + return ret; +} + +static bool is_valid_dynamic_minor(int minor) +{ + if (minor < 0) + return false; + if (minor =3D=3D MISC_DYNAMIC_MINOR) + return false; + if (minor >=3D 0 && minor <=3D 15) + return false; + if (minor >=3D 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 =3D { + .open =3D miscdev_test_open, +}; + +static void __init miscdev_test_can_open(struct kunit *test, struct miscde= vice *misc) +{ + int ret; + struct file *filp; + char *devname; + + devname =3D kasprintf(GFP_KERNEL, "/dev/%s", misc->name); + ret =3D init_mknod(devname, S_IFCHR | 0600, + new_encode_dev(MKDEV(MISC_MAJOR, misc->minor))); + if (ret !=3D 0) + KUNIT_FAIL(test, "failed to create node\n"); + + filp =3D 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 =3D { + .name =3D "misc_test", + .fops =3D &miscdev_test_fops, + }; + int ret; + const struct miscdev_test_case *params =3D test->param_value; + + misc_test.minor =3D params->minor; + + ret =3D misc_register(&misc_test); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, misc_test.minor, params->minor); + + if (ret =3D=3D 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 =3D { + .minor =3D MISC_DYNAMIC_MINOR, + .name =3D "misc_test", + .fops =3D &miscdev_test_fops, + }; + int ret; + + ret =3D misc_register(&misc_test); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(misc_test.minor)); + + if (ret =3D=3D 0) { + miscdev_test_can_open(test, &misc_test); + misc_deregister(&misc_test); + } +} + +static void miscdev_test_twice(struct kunit *test) +{ + struct miscdevice misc_test =3D { + .name =3D "misc_test", + .fops =3D &miscdev_test_fops, + }; + int ret; + const struct miscdev_test_case *params =3D test->param_value; + + misc_test.minor =3D params->minor; + + ret =3D misc_register(&misc_test); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, misc_test.minor, params->minor); + if (ret =3D=3D 0) + misc_deregister(&misc_test); + + ret =3D misc_register(&misc_test); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, misc_test.minor, params->minor); + if (ret =3D=3D 0) + misc_deregister(&misc_test); +} + +static void miscdev_test_duplicate_minor(struct kunit *test) +{ + struct miscdevice misc1 =3D { + .name =3D "misc1", + .fops =3D &miscdev_test_fops, + }; + struct miscdevice misc2 =3D { + .name =3D "misc2", + .fops =3D &miscdev_test_fops, + }; + int ret; + const struct miscdev_test_case *params =3D test->param_value; + + misc1.minor =3D params->minor; + misc2.minor =3D params->minor; + + ret =3D misc_register(&misc1); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, misc1.minor, params->minor); + + ret =3D misc_register(&misc2); + KUNIT_EXPECT_EQ(test, ret, -EBUSY); + if (ret =3D=3D 0) + misc_deregister(&misc2); + + misc_deregister(&misc1); +} + +static void miscdev_test_duplicate_name(struct kunit *test) +{ + struct miscdevice misc1 =3D { + .minor =3D MISC_DYNAMIC_MINOR, + .name =3D "misc1", + .fops =3D &miscdev_test_fops, + }; + struct miscdevice misc2 =3D { + .minor =3D MISC_DYNAMIC_MINOR, + .name =3D "misc1", + .fops =3D &miscdev_test_fops, + }; + int ret; + + ret =3D misc_register(&misc1); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(misc1.minor)); + + ret =3D misc_register(&misc2); + KUNIT_EXPECT_EQ(test, ret, -EEXIST); + if (ret =3D=3D 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 =3D { + .minor =3D MISC_DYNAMIC_MINOR, + .name =3D "misc1", + .fops =3D &miscdev_test_fops, + }; + struct miscdevice misc2 =3D { + .minor =3D MISC_DYNAMIC_MINOR, + .name =3D "misc1", + .fops =3D &miscdev_test_fops, + }; + struct miscdevice misc3 =3D { + .minor =3D MISC_DYNAMIC_MINOR, + .name =3D "misc3", + .fops =3D &miscdev_test_fops, + }; + int ret; + int dyn_minor; + + ret =3D 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 =3D misc_register(&misc3); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(misc3.minor)); + dyn_minor =3D misc3.minor; + misc_deregister(&misc3); + misc3.minor =3D MISC_DYNAMIC_MINOR; + + ret =3D misc_register(&misc2); + KUNIT_EXPECT_EQ(test, ret, -EEXIST); + if (ret =3D=3D 0) + misc_deregister(&misc2); + + /* + * Now check that we can still get the same minor we found before. + */ + ret =3D 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 =3D { + .minor =3D MISC_DYNAMIC_MINOR, + .name =3D "name1", + .fops =3D &miscdev_test_fops, + }; + struct miscdevice miscstat =3D { + .name =3D "name1", + .fops =3D &miscdev_test_fops, + }; + struct miscdevice miscnew =3D { + .name =3D "name2", + .fops =3D &miscdev_test_fops, + }; + int ret; + const struct miscdev_test_case *params =3D test->param_value; + + miscstat.minor =3D params->minor; + miscnew.minor =3D params->minor; + + ret =3D misc_register(&miscdyn); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn.minor)); + + ret =3D misc_register(&miscstat); + KUNIT_EXPECT_EQ(test, ret, -EEXIST); + if (ret =3D=3D 0) + misc_deregister(&miscstat); + + ret =3D misc_register(&miscnew); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, miscnew.minor, params->minor); + if (ret =3D=3D 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 =3D 256; + int i; + + miscdev =3D kunit_kmalloc_array(test, dynamic_minors, + sizeof(struct miscdevice), + GFP_KERNEL | __GFP_ZERO); + + for (i =3D 0; i < dynamic_minors; i++) { + miscdev[i].minor =3D MISC_DYNAMIC_MINOR; + miscdev[i].name =3D kasprintf(GFP_KERNEL, "misc_test%d", i); + miscdev[i].fops =3D &miscdev_test_fops; + ret =3D misc_register(&miscdev[i]); + if (ret !=3D 0) + break; + /* + * This is the bug we are looking for! + * We asked for a dynamic minor and got a minor in the static range spac= e. + */ + if (miscdev[i].minor >=3D 0 && miscdev[i].minor <=3D 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 >=3D 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 =3D { + .name =3D "miscstat", + .fops =3D &miscdev_test_fops, + }; + const int dynamic_minors =3D 256; + int i; + + miscdev =3D kunit_kmalloc_array(test, dynamic_minors, + sizeof(struct miscdevice), + GFP_KERNEL | __GFP_ZERO); + + miscstat.minor =3D miscdev_test_ranges[0].minor; + ret =3D misc_register(&miscstat); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, miscstat.minor, miscdev_test_ranges[0].minor); + + for (i =3D 0; i < dynamic_minors; i++) { + miscdev[i].minor =3D MISC_DYNAMIC_MINOR; + miscdev[i].name =3D kasprintf(GFP_KERNEL, "misc_test%d", i); + miscdev[i].fops =3D &miscdev_test_fops; + ret =3D misc_register(&miscdev[i]); + if (ret !=3D 0) + break; + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdev[i].minor)); + } + + for (i--; i >=3D 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 =3D { + .name =3D "miscstat", + .fops =3D &miscdev_test_fops, + }; + const int dynamic_minors =3D 256; + int i; + + miscdev =3D kunit_kmalloc_array(test, dynamic_minors, + sizeof(struct miscdevice), + GFP_KERNEL | __GFP_ZERO); + + for (i =3D 0; i < dynamic_minors; i++) { + miscdev[i].minor =3D MISC_DYNAMIC_MINOR; + miscdev[i].name =3D kasprintf(GFP_KERNEL, "misc_test%d", i); + miscdev[i].fops =3D &miscdev_test_fops; + ret =3D misc_register(&miscdev[i]); + if (ret !=3D 0) + break; + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdev[i].minor)); + } + + KUNIT_EXPECT_EQ(test, ret, 0); + + miscstat.minor =3D miscdev_test_ranges[0].minor; + ret =3D misc_register(&miscstat); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, miscstat.minor, miscdev_test_ranges[0].minor); + if (ret =3D=3D 0) + misc_deregister(&miscstat); + + for (i--; i >=3D 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 =3D { + .name =3D "miscdyn", + .minor =3D MISC_DYNAMIC_MINOR, + .fops =3D &miscdev_test_fops, + }; + struct miscdevice miscstat =3D { + .name =3D "miscstat", + .fops =3D &miscdev_test_fops, + }; + + ret =3D 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 =3D miscdyn.minor; + ret =3D misc_register(&miscstat); + KUNIT_EXPECT_EQ(test, ret, -EBUSY); + if (ret =3D=3D 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 =3D { + .name =3D "miscdyn", + .minor =3D MISC_DYNAMIC_MINOR, + .fops =3D &miscdev_test_fops, + }; + struct miscdevice miscstat =3D { + .name =3D "miscstat", + .fops =3D &miscdev_test_fops, + }; + + /* + * Find the first available dynamic minor to use it as a static + * minor later on. + */ + ret =3D misc_register(&miscdyn); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn.minor)); + miscstat.minor =3D miscdyn.minor; + misc_deregister(&miscdyn); + + ret =3D 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 =3D MISC_DYNAMIC_MINOR; + ret =3D 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 =3D=3D 0) + misc_deregister(&miscdyn); + + miscdev_test_can_open(test, &miscstat); + + misc_deregister(&miscstat); +} + static struct kunit_case test_cases[] =3D { 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), {} }; =20 static struct kunit_suite test_suite =3D { - .name =3D "misc_minor_test", + .name =3D "miscdev", + .suite_init =3D miscdev_find_minors, .test_cases =3D test_cases, }; kunit_test_suite(test_suite); =20 +static struct kunit_case __refdata test_init_cases[] =3D { + 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 =3D { + .name =3D "miscdev_init", + .suite_init =3D miscdev_find_minors, + .test_cases =3D test_init_cases, +}; +kunit_test_init_section_suite(test_init_suite); + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Vimal Agrawal"); -MODULE_DESCRIPTION("misc minor testing"); +MODULE_AUTHOR("Thadeu Lima de Souza Cascardo "); +MODULE_DESCRIPTION("Test module for misc character devices"); --=20 2.47.2