Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/sys/arch/x86/pci Make pci_conf_read(9) and pci_conf_write(9)...



details:   https://anonhg.NetBSD.org/src/rev/e2ea9253d614
branches:  trunk
changeset: 754325:e2ea9253d614
user:      dyoung <dyoung%NetBSD.org@localhost>
date:      Tue Apr 27 23:33:14 2010 +0000

description:
Make pci_conf_read(9) and pci_conf_write(9) re-entrant so that the
kernel can use them in an NMI trap handler.  Only one CPU can be
in _read() or _write() at once.  However, on any single CPU, more
than one thread of execution (LWP, interrupt handler, trap handler)
may be in _read() or _write() at once, because each thread saves
and restores the PCI configuration-access state.

diffstat:

 sys/arch/x86/pci/pci_machdep.c |  110 ++++++++++++++++++++++++++++++----------
 1 files changed, 81 insertions(+), 29 deletions(-)

diffs (195 lines):

diff -r 87cff579df7f -r e2ea9253d614 sys/arch/x86/pci/pci_machdep.c
--- a/sys/arch/x86/pci/pci_machdep.c    Tue Apr 27 23:30:29 2010 +0000
+++ b/sys/arch/x86/pci/pci_machdep.c    Tue Apr 27 23:33:14 2010 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: pci_machdep.c,v 1.41 2010/03/14 20:19:06 dyoung Exp $  */
+/*     $NetBSD: pci_machdep.c,v 1.42 2010/04/27 23:33:14 dyoung Exp $  */
 
 /*-
  * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
@@ -73,7 +73,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: pci_machdep.c,v 1.41 2010/03/14 20:19:06 dyoung Exp $");
+__KERNEL_RCSID(0, "$NetBSD: pci_machdep.c,v 1.42 2010/04/27 23:33:14 dyoung Exp $");
 
 #include <sys/types.h>
 #include <sys/param.h>
@@ -82,6 +82,7 @@
 #include <sys/errno.h>
 #include <sys/device.h>
 #include <sys/bus.h>
+#include <sys/cpu.h>
 
 #include <uvm/uvm_extern.h>
 
@@ -129,26 +130,24 @@
 static int pci_mode = -1;
 #endif
 
+struct pci_conf_lock {
+       uint32_t cl_cpuno;      /* 0: unlocked
+                                * 1 + n: locked by CPU n (0 <= n)
+                                */
+       uint32_t cl_sel;        /* the address that's being read. */
+};
+
+static void pci_conf_unlock(struct pci_conf_lock *);
+static uint32_t pci_conf_selector(pcitag_t, int);
+static unsigned int pci_conf_port(pcitag_t, int);
+static void pci_conf_select(uint32_t);
+static void pci_conf_lock(struct pci_conf_lock *, uint32_t);
 static void pci_bridge_hook(pci_chipset_tag_t, pcitag_t, void *);
 struct pci_bridge_hook_arg {
        void (*func)(pci_chipset_tag_t, pcitag_t, void *); 
        void *arg; 
 }; 
 
-__cpu_simple_lock_t pci_conf_lock = __SIMPLELOCK_UNLOCKED;
-
-#define        PCI_CONF_LOCK(s)                                                \
-do {                                                                   \
-       (s) = splhigh();                                                \
-       __cpu_simple_lock(&pci_conf_lock);                              \
-} while (0)
-
-#define        PCI_CONF_UNLOCK(s)                                              \
-do {                                                                   \
-       __cpu_simple_unlock(&pci_conf_lock);                            \
-       splx((s));                                                      \
-} while (0)
-
 #define        PCI_MODE1_ENABLE        0x80000000UL
 #define        PCI_MODE1_ADDRESS_REG   0x0cf8
 #define        PCI_MODE1_DATA_REG      0x0cfc
@@ -243,6 +242,63 @@
 };
 #endif
 
+static struct pci_conf_lock cl0 = {
+         .cl_cpuno = 0UL
+       , .cl_sel = 0UL
+};
+
+static struct pci_conf_lock * const cl = &cl0;
+
+static void
+pci_conf_lock(struct pci_conf_lock *ocl, uint32_t sel)
+{
+       uint32_t cpuno;
+
+       KASSERT(sel != 0);
+
+       kpreempt_disable();
+       cpuno = cpu_number() + 1;
+       /* If the kernel enters pci_conf_lock() through an interrupt
+        * handler, then the CPU may already hold the lock.
+        *
+        * If the CPU does not already hold the lock, spin until
+        * we can acquire it.
+        */
+       if (cpuno == cl->cl_cpuno) {
+               ocl->cl_cpuno = cpuno;
+       } else {
+               ocl->cl_cpuno = 0;
+               while (atomic_cas_32(&cl->cl_cpuno, 0, cpuno) != 0)
+                       ;
+       }
+
+       /* Only one CPU can be here, so an interlocked atomic_swap(3)
+        * is not necessary.
+        *
+        * Evaluating atomic_cas_32_ni()'s argument, cl->cl_sel,
+        * and applying atomic_cas_32_ni() is not an atomic operation,
+        * however, any interrupt that, in the middle of the
+        * operation, modifies cl->cl_sel, will also restore
+        * cl->cl_sel.  So cl->cl_sel will have the same value when
+        * we apply atomic_cas_32_ni() as when we evaluated it,
+        * before.
+        */
+       ocl->cl_sel = atomic_cas_32_ni(&cl->cl_sel, cl->cl_sel, sel);
+       pci_conf_select(sel);
+}
+
+static void
+pci_conf_unlock(struct pci_conf_lock *ocl)
+{
+       uint32_t sel;
+
+       sel = atomic_cas_32_ni(&cl->cl_sel, cl->cl_sel, ocl->cl_sel);
+       pci_conf_select(ocl->cl_sel);
+       if (ocl->cl_cpuno != cl->cl_cpuno)
+               atomic_cas_32(&cl->cl_cpuno, cl->cl_cpuno, ocl->cl_cpuno);
+       kpreempt_enable();
+}
+
 static uint32_t
 pci_conf_selector(pcitag_t tag, int reg)
 {
@@ -277,16 +333,16 @@
 }
 
 static void
-pci_conf_select(uint32_t addr)
+pci_conf_select(uint32_t sel)
 {
        pcitag_t tag;
 
        switch (pci_mode) {
        case 1:
-               outl(PCI_MODE1_ADDRESS_REG, addr);
+               outl(PCI_MODE1_ADDRESS_REG, sel);
                return;
        case 2:
-               tag.mode1 = addr;
+               tag.mode1 = sel;
                outb(PCI_MODE2_ENABLE_REG, tag.mode2.enable);
                if (tag.mode2.enable != 0)
                        outb(PCI_MODE2_FORWARD_REG, tag.mode2.forward);
@@ -417,7 +473,7 @@
     int reg)
 {
        pcireg_t data;
-       int s;
+       struct pci_conf_lock ocl;
 
        KASSERT((reg & 0x3) == 0);
 
@@ -437,11 +493,9 @@
        }
 #endif
 
-       PCI_CONF_LOCK(s);
-       pci_conf_select(pci_conf_selector(tag, reg));
+       pci_conf_lock(&ocl, pci_conf_selector(tag, reg));
        data = inl(pci_conf_port(tag, reg));
-       pci_conf_select(0);
-       PCI_CONF_UNLOCK(s);
+       pci_conf_unlock(&ocl);
        return data;
 }
 
@@ -449,7 +503,7 @@
 pci_conf_write(pci_chipset_tag_t pc, pcitag_t tag, int reg,
     pcireg_t data)
 {
-       int s;
+       struct pci_conf_lock ocl;
 
        KASSERT((reg & 0x3) == 0);
 
@@ -473,11 +527,9 @@
        }
 #endif
 
-       PCI_CONF_LOCK(s);
-       pci_conf_select(pci_conf_selector(tag, reg));
+       pci_conf_lock(&ocl, pci_conf_selector(tag, reg));
        outl(pci_conf_port(tag, reg), data);
-       pci_conf_select(0);
-       PCI_CONF_UNLOCK(s);
+       pci_conf_unlock(&ocl);
 }
 
 void



Home | Main Index | Thread Index | Old Index