Skip to content

Commit 7bb8c40

Browse files
maurizio-lombardikeithbusch
authored andcommitted
nvme: add support for dynamic quirk configuration via module parameter
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: enable BOGUS_NID and BROKEN_MSI, disable DEALLOCATE_ZEROES: $ modprobe nvme quirks=7170:2210:bogus_nid,broken_msi,^deallocate_zeroes Tested-by: Daniel Wagner <dwagner@suse.de> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Maurizio Lombardi <mlombard@redhat.com> Signed-off-by: Daniel Wagner <dwagner@suse.de> Signed-off-by: Keith Busch <kbusch@kernel.org>
1 parent b84bb7b commit 7bb8c40

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

Documentation/admin-guide/kernel-parameters.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
TPM TPM drivers are enabled.
7575
UMS USB Mass Storage support is enabled.
7676
USB USB support is enabled.
77+
NVME NVMe support is enabled
7778
USBHID USB Human Interface Device support is enabled.
7879
V4L Video For Linux support is enabled.
7980
VGA The VGA console has been enabled.
@@ -4671,6 +4672,18 @@ Kernel parameters
46714672
This can be set from sysctl after boot.
46724673
See Documentation/admin-guide/sysctl/vm.rst for details.
46734674

4675+
nvme.quirks= [NVME] A list of quirk entries to augment the built-in
4676+
nvme quirk list. List entries are separated by a
4677+
'-' character.
4678+
Each entry has the form VendorID:ProductID:quirk_names.
4679+
The IDs are 4-digits hex numbers and quirk_names is a
4680+
list of quirk names separated by commas. A quirk name
4681+
can be prefixed by '^', meaning that the specified
4682+
quirk must be disabled.
4683+
4684+
Example:
4685+
nvme.quirks=7710:2267:bogus_nid,^identify_cns-9900:7711:broken_msi
4686+
46744687
ohci1394_dma=early [HW,EARLY] enable debugging via the ohci1394 driver.
46754688
See Documentation/core-api/debugging-via-ohci1394.rst for more
46764689
info.

drivers/nvme/host/pci.c

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@
7272
static_assert(MAX_PRP_RANGE / NVME_CTRL_PAGE_SIZE <=
7373
(1 /* prp1 */ + NVME_MAX_NR_DESCRIPTORS * PRPS_PER_PAGE));
7474

75+
struct quirk_entry {
76+
u16 vendor_id;
77+
u16 dev_id;
78+
u32 enabled_quirks;
79+
u32 disabled_quirks;
80+
};
81+
7582
static int use_threaded_interrupts;
7683
module_param(use_threaded_interrupts, int, 0444);
7784

@@ -102,6 +109,142 @@ static unsigned int io_queue_depth = 1024;
102109
module_param_cb(io_queue_depth, &io_queue_depth_ops, &io_queue_depth, 0644);
103110
MODULE_PARM_DESC(io_queue_depth, "set io queue depth, should >= 2 and < 4096");
104111

112+
static struct quirk_entry *nvme_pci_quirk_list;
113+
static unsigned int nvme_pci_quirk_count;
114+
115+
/* Helper to parse individual quirk names */
116+
static int nvme_parse_quirk_names(char *quirk_str, struct quirk_entry *entry)
117+
{
118+
int i;
119+
size_t field_len;
120+
bool disabled, found;
121+
char *p = quirk_str, *field;
122+
123+
while ((field = strsep(&p, ",")) && *field) {
124+
disabled = false;
125+
found = false;
126+
127+
if (*field == '^') {
128+
/* Skip the '^' character */
129+
disabled = true;
130+
field++;
131+
}
132+
133+
field_len = strlen(field);
134+
for (i = 0; i < 32; i++) {
135+
unsigned int bit = 1U << i;
136+
char *q_name = nvme_quirk_name(bit);
137+
size_t q_len = strlen(q_name);
138+
139+
if (!strcmp(q_name, "unknown"))
140+
break;
141+
142+
if (!strcmp(q_name, field) &&
143+
q_len == field_len) {
144+
if (disabled)
145+
entry->disabled_quirks |= bit;
146+
else
147+
entry->enabled_quirks |= bit;
148+
found = true;
149+
break;
150+
}
151+
}
152+
153+
if (!found) {
154+
pr_err("nvme: unrecognized quirk %s\n", field);
155+
return -EINVAL;
156+
}
157+
}
158+
return 0;
159+
}
160+
161+
/* Helper to parse a single VID:DID:quirk_names entry */
162+
static int nvme_parse_quirk_entry(char *s, struct quirk_entry *entry)
163+
{
164+
char *field;
165+
166+
field = strsep(&s, ":");
167+
if (!field || kstrtou16(field, 16, &entry->vendor_id))
168+
return -EINVAL;
169+
170+
field = strsep(&s, ":");
171+
if (!field || kstrtou16(field, 16, &entry->dev_id))
172+
return -EINVAL;
173+
174+
field = strsep(&s, ":");
175+
if (!field)
176+
return -EINVAL;
177+
178+
return nvme_parse_quirk_names(field, entry);
179+
}
180+
181+
static int quirks_param_set(const char *value, const struct kernel_param *kp)
182+
{
183+
int count, err, i;
184+
struct quirk_entry *qlist;
185+
char *field, *val, *sep_ptr;
186+
187+
err = param_set_copystring(value, kp);
188+
if (err)
189+
return err;
190+
191+
val = kstrdup(value, GFP_KERNEL);
192+
if (!val)
193+
return -ENOMEM;
194+
195+
if (!*val)
196+
goto out_free_val;
197+
198+
count = 1;
199+
for (i = 0; val[i]; i++) {
200+
if (val[i] == '-')
201+
count++;
202+
}
203+
204+
qlist = kcalloc(count, sizeof(*qlist), GFP_KERNEL);
205+
if (!qlist) {
206+
err = -ENOMEM;
207+
goto out_free_val;
208+
}
209+
210+
i = 0;
211+
sep_ptr = val;
212+
while ((field = strsep(&sep_ptr, "-"))) {
213+
if (nvme_parse_quirk_entry(field, &qlist[i])) {
214+
pr_err("nvme: failed to parse quirk string %s\n",
215+
value);
216+
goto out_free_qlist;
217+
}
218+
219+
i++;
220+
}
221+
222+
nvme_pci_quirk_count = count;
223+
nvme_pci_quirk_list = qlist;
224+
goto out_free_val;
225+
226+
out_free_qlist:
227+
kfree(qlist);
228+
out_free_val:
229+
kfree(val);
230+
return err;
231+
}
232+
233+
static char quirks_param[128];
234+
static const struct kernel_param_ops quirks_param_ops = {
235+
.set = quirks_param_set,
236+
.get = param_get_string,
237+
};
238+
239+
static struct kparam_string quirks_param_string = {
240+
.maxlen = sizeof(quirks_param),
241+
.string = quirks_param,
242+
};
243+
244+
module_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0444);
245+
MODULE_PARM_DESC(quirks, "Enable/disable NVMe quirks by specifying "
246+
"quirks=VID:DID:quirk_names");
247+
105248
static int io_queue_count_set(const char *val, const struct kernel_param *kp)
106249
{
107250
unsigned int n;
@@ -3439,12 +3582,25 @@ static unsigned long check_vendor_combination_bug(struct pci_dev *pdev)
34393582
return 0;
34403583
}
34413584

3585+
static struct quirk_entry *detect_dynamic_quirks(struct pci_dev *pdev)
3586+
{
3587+
int i;
3588+
3589+
for (i = 0; i < nvme_pci_quirk_count; i++)
3590+
if (pdev->vendor == nvme_pci_quirk_list[i].vendor_id &&
3591+
pdev->device == nvme_pci_quirk_list[i].dev_id)
3592+
return &nvme_pci_quirk_list[i];
3593+
3594+
return NULL;
3595+
}
3596+
34423597
static struct nvme_dev *nvme_pci_alloc_dev(struct pci_dev *pdev,
34433598
const struct pci_device_id *id)
34443599
{
34453600
unsigned long quirks = id->driver_data;
34463601
int node = dev_to_node(&pdev->dev);
34473602
struct nvme_dev *dev;
3603+
struct quirk_entry *qentry;
34483604
int ret = -ENOMEM;
34493605

34503606
dev = kzalloc_node(struct_size(dev, descriptor_pools, nr_node_ids),
@@ -3476,6 +3632,11 @@ static struct nvme_dev *nvme_pci_alloc_dev(struct pci_dev *pdev,
34763632
"platform quirk: setting simple suspend\n");
34773633
quirks |= NVME_QUIRK_SIMPLE_SUSPEND;
34783634
}
3635+
qentry = detect_dynamic_quirks(pdev);
3636+
if (qentry) {
3637+
quirks |= qentry->enabled_quirks;
3638+
quirks &= ~qentry->disabled_quirks;
3639+
}
34793640
ret = nvme_init_ctrl(&dev->ctrl, &pdev->dev, &nvme_pci_ctrl_ops,
34803641
quirks);
34813642
if (ret)
@@ -4074,6 +4235,7 @@ static int __init nvme_init(void)
40744235

40754236
static void __exit nvme_exit(void)
40764237
{
4238+
kfree(nvme_pci_quirk_list);
40774239
pci_unregister_driver(&nvme_driver);
40784240
flush_workqueue(nvme_wq);
40794241
}

0 commit comments

Comments
 (0)