From: Maurizio Lombardi <mlombard@redhat.com>
To: kbusch@kernel.org
Cc: hch@lst.de, linux-nvme@lists.infradead.org, dwagner@suse.de,
hare@suse.de, jmeneghi@redhat.com, emilne@redhat.com,
mlombard@bsdbackstore.eu
Subject: [PATCH 1/1] nvme: add support for dynamic quirk configuration via module parameter
Date: Wed, 28 Jan 2026 11:33:18 +0100 [thread overview]
Message-ID: <20260128103318.76842-2-mlombard@redhat.com> (raw)
In-Reply-To: <20260128103318.76842-1-mlombard@redhat.com>
Introduce support for enabling or disabling specific NVMe quirks at
module load time through the `quirks` module parameter.
This mechanism allows users to apply known quirks dynamically
based on the device's PCI vendor and device IDs,
without requiring to add hardcoded entries in the driver
and recompiling the kernel.
While the generic PCI new_id sysfs interface exists for
dynamic configuration, it is insufficient for scenarios where
the system fails to boot (for example, this has been reported to happen
because of the bogus_nid quirk). The new_id attribute is writable
only after the system has booted and sysfs is mounted.
The `quirks` parameter accepts a list of quirk
specifications separated by a '-' character in the following format:
<VID>:<DID>:<quirk_names>[-<VID>:<DID>:<quirk_names>-..]
Each quirk is represented by its name and can be prefixed with
`^` to indicate that the quirk should be disabled.
Quirk names are separated by a ',' character.
Example usage (enable BOGUS_NID and BROKEN_MSI, disable DEALLOCATE_ZEROES):
$ modprobe nvme quirks=7170:2210:bogus_nid,broken_msi,^deallocate_zeroes
Signed-off-by: Maurizio Lombardi <mlombard@redhat.com>
Tested-by: Daniel Wagner <dwagner@suse.de>
Signed-off-by: Daniel Wagner <dwagner@suse.de>
---
.../admin-guide/kernel-parameters.txt | 13 ++
drivers/nvme/host/pci.c | 154 ++++++++++++++++++
2 files changed, 167 insertions(+)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index a8d0afde7f85..e559648f97e6 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -74,6 +74,7 @@
TPM TPM drivers are enabled.
UMS USB Mass Storage support is enabled.
USB USB support is enabled.
+ NVME NVMe support is enabled
USBHID USB Human Interface Device support is enabled.
V4L Video For Linux support is enabled.
VGA The VGA console has been enabled.
@@ -4671,6 +4672,18 @@ Kernel parameters
This can be set from sysctl after boot.
See Documentation/admin-guide/sysctl/vm.rst for details.
+ nvme.quirks= [NVME] A list of quirk entries to augment the built-in
+ nvme quirk list. List entries are separated by a
+ '-' character.
+ Each entry has the form VendorID:ProductID:quirk_names.
+ The IDs are 4-digits hex numbers and quirk_names is a
+ list of quirk names separated by commas. A quirk name can
+ be prefixed by '^', meaning that the specified quirk must
+ be disabled.
+
+ Example:
+ nvme.quirks=7710:2267:bogus_nid,^identify_cns-9900:7711:broken_msi
+
ohci1394_dma=early [HW,EARLY] enable debugging via the ohci1394 driver.
See Documentation/core-api/debugging-via-ohci1394.rst for more
info.
diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c
index 9fc4a60280a0..512c5cecc6f0 100644
--- a/drivers/nvme/host/pci.c
+++ b/drivers/nvme/host/pci.c
@@ -72,6 +72,13 @@
static_assert(MAX_PRP_RANGE / NVME_CTRL_PAGE_SIZE <=
(1 /* prp1 */ + NVME_MAX_NR_DESCRIPTORS * PRPS_PER_PAGE));
+struct quirk_entry {
+ u16 vendor_id;
+ u16 dev_id;
+ u32 enabled_quirks;
+ u32 disabled_quirks;
+};
+
static int use_threaded_interrupts;
module_param(use_threaded_interrupts, int, 0444);
@@ -102,6 +109,132 @@ static unsigned int io_queue_depth = 1024;
module_param_cb(io_queue_depth, &io_queue_depth_ops, &io_queue_depth, 0644);
MODULE_PARM_DESC(io_queue_depth, "set io queue depth, should >= 2 and < 4096");
+static int quirks_param_set(const char *value, const struct kernel_param *kp);
+static char quirks_param[128];
+static const struct kernel_param_ops quirks_param_ops = {
+ .set = quirks_param_set,
+ .get = param_get_string,
+};
+
+static struct kparam_string quirks_param_string = {
+ .maxlen = sizeof(quirks_param),
+ .string = quirks_param,
+};
+
+static struct quirk_entry *quirk_list;
+static unsigned int quirk_count;
+module_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0644);
+MODULE_PARM_DESC(quirks, "Enable/disable NVMe quirks by specifying quirks=vendorID:deviceID:quirk_names");
+
+static int quirks_param_set(const char *value, const struct kernel_param *kp)
+{
+ char *val, *outer_ptr, *inner_ptr, *field;
+ char *q_name;
+ u16 vid, did;
+ u32 *flags;
+ size_t i = 0, q_len, field_len;
+ int err, b;
+
+ val = kstrdup(value, GFP_KERNEL);
+ if (!val)
+ return -ENOMEM;
+
+ err = param_set_copystring(val, kp);
+ if (err)
+ goto exit;
+
+ kfree(quirk_list);
+ quirk_list = NULL;
+
+ if (!*val) {
+ quirk_count = 0;
+ goto exit;
+ }
+
+ for (quirk_count = 1, i = 0; val[i]; i++)
+ if (val[i] == '-')
+ quirk_count++;
+
+ quirk_list = kcalloc(quirk_count, sizeof(struct quirk_entry),
+ GFP_KERNEL);
+ if (!quirk_list) {
+ quirk_count = 0;
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ i = 0;
+ outer_ptr = val;
+ while ((field = strsep(&outer_ptr, "-"))) {
+ inner_ptr = field;
+
+ /* Each entry consists of VID:DID:quirk_names */
+ field = strsep(&inner_ptr, ":");
+ if (!field) {
+ pr_err("nvme: malformed quirks string: %s\n",
+ value);
+ break;
+ }
+
+ if (kstrtou16(field, 16, &vid)) {
+ pr_err("nvme: malformed vendor id: %s\n", field);
+ break;
+ }
+
+ field = strsep(&inner_ptr, ":");
+ if (!field) {
+ pr_err("nvme: malformed quirks string: %s\n",
+ value);
+ break;
+ }
+
+ if (kstrtou16(field, 16, &did)) {
+ pr_err("nvme: malformed device id: %s\n", field);
+ break;
+ }
+
+ while ((field = strsep(&inner_ptr, ",")) && *field) {
+
+ /* Each entry consists of a quirk name */
+
+ if (*field == '^') {
+ flags = &quirk_list[i].disabled_quirks;
+ field++;
+ } else {
+ flags = &quirk_list[i].enabled_quirks;
+ }
+
+ field_len = strlen(field);
+ for (b = 0; ; ++b) {
+ q_name = nvme_quirk_name(BIT(b));
+ q_len = strlen(q_name);
+
+ if (!strcmp(q_name, "unknown")) {
+ pr_err("nvme: unrecognized quirk %s\n",
+ field);
+ break;
+ }
+ if (!strncmp(q_name, field, q_len) &&
+ q_len == field_len) {
+ *flags |= BIT(b);
+ break;
+ }
+ }
+ }
+
+ quirk_list[i].vendor_id = vid;
+ quirk_list[i].dev_id = did;
+ ++i;
+ }
+
+ if (i < quirk_count)
+ quirk_count = i;
+
+exit:
+ kfree(val);
+ return err;
+}
+
static int io_queue_count_set(const char *val, const struct kernel_param *kp)
{
unsigned int n;
@@ -3439,12 +3572,27 @@ static unsigned long check_vendor_combination_bug(struct pci_dev *pdev)
return 0;
}
+static struct quirk_entry *detect_dynamic_quirks(struct pci_dev *pdev)
+{
+ int i;
+
+ for (i = 0; i < quirk_count; ++i) {
+ if (pdev->vendor == quirk_list[i].vendor_id &&
+ pdev->device == quirk_list[i].dev_id) {
+ return &quirk_list[i];
+ }
+ }
+
+ return NULL;
+}
+
static struct nvme_dev *nvme_pci_alloc_dev(struct pci_dev *pdev,
const struct pci_device_id *id)
{
unsigned long quirks = id->driver_data;
int node = dev_to_node(&pdev->dev);
struct nvme_dev *dev;
+ struct quirk_entry *qentry;
int ret = -ENOMEM;
dev = kzalloc_node(struct_size(dev, descriptor_pools, nr_node_ids),
@@ -3476,6 +3624,11 @@ static struct nvme_dev *nvme_pci_alloc_dev(struct pci_dev *pdev,
"platform quirk: setting simple suspend\n");
quirks |= NVME_QUIRK_SIMPLE_SUSPEND;
}
+ qentry = detect_dynamic_quirks(pdev);
+ if (qentry) {
+ quirks |= qentry->enabled_quirks;
+ quirks &= ~(qentry->disabled_quirks);
+ }
ret = nvme_init_ctrl(&dev->ctrl, &pdev->dev, &nvme_pci_ctrl_ops,
quirks);
if (ret)
@@ -4074,6 +4227,7 @@ static int __init nvme_init(void)
static void __exit nvme_exit(void)
{
+ kfree(quirk_list);
pci_unregister_driver(&nvme_driver);
flush_workqueue(nvme_wq);
}
--
2.52.0
next prev parent reply other threads:[~2026-01-28 10:35 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-28 10:33 [PATCH 0/1] Enable/Disable NVMe quirks via module parameter Maurizio Lombardi
2026-01-28 10:33 ` Maurizio Lombardi [this message]
2026-02-02 7:17 ` [PATCH 1/1] nvme: add support for dynamic quirk configuration " Christoph Hellwig
2026-02-02 17:38 ` Maurizio Lombardi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260128103318.76842-2-mlombard@redhat.com \
--to=mlombard@redhat.com \
--cc=dwagner@suse.de \
--cc=emilne@redhat.com \
--cc=hare@suse.de \
--cc=hch@lst.de \
--cc=jmeneghi@redhat.com \
--cc=kbusch@kernel.org \
--cc=linux-nvme@lists.infradead.org \
--cc=mlombard@bsdbackstore.eu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox