[PATCH 1/1] nvme: add support for dynamic quirk configuration via module parameter
Maurizio Lombardi
mlombard at redhat.com
Wed Jan 28 02:33:18 PST 2026
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 at redhat.com>
Tested-by: Daniel Wagner <dwagner at suse.de>
Signed-off-by: Daniel Wagner <dwagner at 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
More information about the Linux-nvme
mailing list