PCI: epf-test: Improve handling of command and status registers
authorDamien Le Moal <dlemoal@kernel.org>
Sat, 15 Apr 2023 02:35:34 +0000 (11:35 +0900)
committerBjorn Helgaas <bhelgaas@google.com>
Fri, 23 Jun 2023 20:02:12 +0000 (15:02 -0500)
The pci-epf-test driver uses the test register BAR memory directly to get
and execute a test registers set by the RC side and defined using a struct
pci_epf_test_reg. This direct use relies on using the register BAR address
as a pointer to a struct pci_epf_test_reg to execute the test case and to
send back the test result through the status field of struct
pci_epf_test_reg. In practice, the status field is always updated before an
interrupt is raised in pci_epf_test_raise_irq(), to ensure that the RC side
sees the updated status when receiving an interrupt.

However, such assignment direct access does not ensure that changes to the
status register make it to memory, and so visible to the host, before an
interrupt is raised, thus potentially resulting in the RC host not seeing
the correct status result for a test.

Avoid this potential problem by using READ_ONCE()/WRITE_ONCE() when
accessing the command and status fields of a pci_epf_test_reg structure.
This ensure that a test start (pci_epf_test_cmd_handler() function) and
completion (with the function pci_epf_test_raise_irq()) achieve a correct
synchronization with the MMIO register accesses on the RC host.

Link: https://lore.kernel.org/r/20230415023542.77601-10-dlemoal@kernel.org
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
drivers/pci/endpoint/functions/pci-epf-test.c

index ee90ba3..fa48e9b 100644 (file)
@@ -613,9 +613,14 @@ static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test,
        struct pci_epf *epf = epf_test->epf;
        struct device *dev = &epf->dev;
        struct pci_epc *epc = epf->epc;
+       u32 status = reg->status | STATUS_IRQ_RAISED;
        int count;
 
-       reg->status |= STATUS_IRQ_RAISED;
+       /*
+        * Set the status before raising the IRQ to ensure that the host sees
+        * the updated value when it gets the IRQ.
+        */
+       WRITE_ONCE(reg->status, status);
 
        switch (reg->irq_type) {
        case IRQ_TYPE_LEGACY:
@@ -659,12 +664,12 @@ static void pci_epf_test_cmd_handler(struct work_struct *work)
        enum pci_barno test_reg_bar = epf_test->test_reg_bar;
        struct pci_epf_test_reg *reg = epf_test->reg[test_reg_bar];
 
-       command = reg->command;
+       command = READ_ONCE(reg->command);
        if (!command)
                goto reset_handler;
 
-       reg->command = 0;
-       reg->status = 0;
+       WRITE_ONCE(reg->command, 0);
+       WRITE_ONCE(reg->status, 0);
 
        if (reg->irq_type > IRQ_TYPE_MSIX) {
                dev_err(dev, "Failed to detect IRQ type\n");