Module Name:    src
Committed By:   dyoung
Date:           Tue Apr 27 23:33:14 UTC 2010

Modified Files:
        src/sys/arch/x86/pci: pci_machdep.c

Log Message:
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.


To generate a diff of this commit:
cvs rdiff -u -r1.41 -r1.42 src/sys/arch/x86/pci/pci_machdep.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/x86/pci/pci_machdep.c
diff -u src/sys/arch/x86/pci/pci_machdep.c:1.41 src/sys/arch/x86/pci/pci_machdep.c:1.42
--- src/sys/arch/x86/pci/pci_machdep.c:1.41	Sun Mar 14 20:19:06 2010
+++ src/sys/arch/x86/pci/pci_machdep.c	Tue Apr 27 23:33:14 2010
@@ -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

Reply via email to