Skip to contentSkip to conversation

ISA DMA controller hangs

PATCH New Pierre Ossman 3 participants 13 messages May 28, 2005 – Jun 30, 2005

lore
Share & tools
lore
#0 lore raw

I've been having some problems with ISA DMA transfers failing. They work fine until the machine does a suspend-to-ram. After that all DMA transfers stall. Does perhaps the DMA controller need a kick in the *** after a suspend? I'm no expert on the ISA DMA controller so I could use some help here.

Rgds
Pierre

#1 lore raw
Quoted 5 lines · Pierre Ossman

I've been having some problems with ISA DMA transfers failing. They work fine until the machine does a suspend-to-ram. After that all DMA transfers stall. Does perhaps the DMA controller need a kick in the *** after a suspend? I'm no expert on the ISA DMA controller so I could use some help here.

The DMA controller has some bits of state which are potentially in need of restoration as well as a need to ensure you don't suspend while it is running I would imagine. Even for bus masters I believe you would need to restore the DMA enable bits.

See include/asm-i386/dma.h but note that some registers have side effects and the dma_outb etc are used to get required delays on some PCs

Alan

#2 lore raw
Quoted 7 lines · Alan Cox

The DMA controller has some bits of state which are potentially in need of restoration as well as a need to ensure you don't suspend while it is running I would imagine. Even for bus masters I believe you would need to restore the DMA enable bits.

I avoid using DMA during suspend to make sure it doesn't get into some wierd state, but that doesn't solve the problem.

I added some debug output to the driver, dumping the DMA controller's registers before and after suspend and it seems it goes completely apeshit. The registers are filled with, what it seems, random data. The reason it stops working seems to be that channel 4 gets disabled killing the cascaded channels. I'm going to try and confirm this today.

Now for the solution. Reseting the DMA controller from a device driver isn't really a good solution. I had a look in dma.c but it didn't seem to have any place to attach resume/suspend functions. Also, I couldn't find where DMA channel 4 is enabled during startup. Or does the kernel count on BIOS doing this?

Rgds
Pierre

#3 lore raw
Quoted 7 lines · Pierre Ossman

I added some debug output to the driver, dumping the DMA controller's registers before and after suspend and it seems it goes completely apeshit. The registers are filled with, what it seems, random data. The reason it stops working seems to be that channel 4 gets disabled killing the cascaded channels. I'm going to try and confirm this today.

Confirmed. By explicitly reenabling DMA channel 4 in the driver on resumt the DMA transfers work fine.

#4 lore raw

So how do we solve this problem? We should do a master clear and then enable channel 4 after a suspend. The question is where. I see three possible places:

* In the suspend code in kernel/power.
* In the driver actually handling the suspend (ACPI/APM/etc.).
* Via the device layer by adding a device for the DMA controller.

Which would be the preferred solution?

Rgds
Pierre

#5 lore raw
Quoted 9 lines · Pierre Ossman

So how do we solve this problem? We should do a master clear and then enable channel 4 after a suspend. The question is where. I see three possible places:

* In the suspend code in kernel/power.
* In the driver actually handling the suspend (ACPI/APM/etc.).
* Via the device layer by adding a device for the DMA controller.

Which would be the preferred solution?

Shouldn't there be a system device for the DMA controller? I think that should have appropriate hooks into the power management system to do the necessary magic to restore whatever's needed - just like we do for the PIC.

Signature · 3 lines
Russell King
 Linux kernel    2.6 ARM Linux   - http://www.arm.linux.org.uk/
 maintainer of:  2.6 Serial core
#6 lore raw
Quoted 8 lines · Russell King

Shouldn't there be a system device for the DMA controller? I think that should have appropriate hooks into the power management system to do the necessary magic to restore whatever's needed - just like we do for the PIC.

I'll have a look at how the PIC is handled then. Any corner cases I should be aware of?

Rgds
Pierre

#7 lore raw
Quoted 8 lines · Pierre Ossman
Quoted 4 lines · Russell King

Shouldn't there be a system device for the DMA controller? I think that should have appropriate hooks into the power management system to do the necessary magic to restore whatever's needed - just like we do for the PIC.

I'll have a look at how the PIC is handled then. Any corner cases I should be aware of?

No idea on that score I'm afraid. Alan may know better.

Signature · 3 lines
Russell King
 Linux kernel    2.6 ARM Linux   - http://www.arm.linux.org.uk/
 maintainer of:  2.6 Serial core
#8 lore raw
In reply to Russell King

Reset the ISA DMA controller into a known state after a suspend. Primary concern was reenabling the cascading DMA channel (4).

If this patch seems ok then I'll make another one for x86_64 (the only other platform with ISA DMA AFAIK).

I couldn't find who was responsible for the i386 arch so I've cc:ed Linus.

Rgds
Pierre

arch/i386/kernel/i8237.c +67 -0
Index: linux-wbsd/arch/i386/kernel/i8237.c
===================================================================
@@ -0,0 +1,67 @@
+/*
+ * i8237.c: 8237A DMA controller suspend functions.
+ *
+ * Written by Pierre Ossman, 2005.
+ */
+
+#include <linux/init.h>
+#include <linux/sysdev.h>
+
+#include <asm/dma.h>
+
+/*
+ * This module just handles suspend/resume issues with the
+ * 8237A DMA controller (used for ISA and LPC).
+ * Allocation is handled in kernel/dma.c and normal usage is
+ * in asm/dma.h.
+ */
+
+static int i8237A_resume(struct sys_device *dev)
+{
+	unsigned long flags;
+	int i;
+	
+	flags = claim_dma_lock();
+	
+	dma_outb(DMA1_RESET_REG, 0);
+	dma_outb(DMA2_RESET_REG, 0);
+
+	for (i = 0;i < 8;i++) {
+	  set_dma_addr(i, 0x000000);
+	  /* DMA count is a bit wierd so this is not 0 */
+	  set_dma_count(i, 1);
+	}
+	
+	/* Enable cascade DMA or channel 0-3 won't work */
+	enable_dma(4);
+	
+	release_dma_lock(flags);
+	
+	return 0;
+}
+
+static int i8237A_suspend(struct sys_device *dev, pm_message_t state)
+{
+	return 0;
+}
+
+static struct sysdev_class i8237_sysdev_class = {
+	set_kset_name("i8237"),
+	.suspend = i8237A_suspend,
+	.resume = i8237A_resume,
+};
+
+static struct sys_device device_i8237A = {
+	.id	= 0,
+	.cls	= &i8237_sysdev_class,
+};
+
+static int __init i8237A_init_sysfs(void)
+{
+	int error = sysdev_class_register(&i8237_sysdev_class);
+	if (!error)
+		error = sysdev_register(&device_i8237A);
+	return error;
+}
+
+device_initcall(i8237A_init_sysfs);
arch/i386/kernel/Makefile +1 -1
Index: linux-wbsd/arch/i386/kernel/Makefile
===================================================================
@@ -7,7 +7,7 @@
 obj-y	:= process.o semaphore.o signal.o entry.o traps.o irq.o vm86.o \
 		ptrace.o time.o ioport.o ldt.o setup.o i8259.o sys_i386.o \
 		pci-dma.o i386_ksyms.o i387.o dmi_scan.o bootflag.o \
-		doublefault.o quirks.o
+		doublefault.o quirks.o i8237.o
 
 obj-y				+= cpu/
 obj-y				+= timers/
#9 lore raw

arch/i386/kernel/i8237.c line 32

Quoted 4 lines · Pierre Ossman
arch/i386/kernel/i8237.c +4 -0
+	for (i = 0;i < 8;i++) {
+	  set_dma_addr(i, 0x000000);
+	  /* DMA count is a bit wierd so this is not 0 */
+	  set_dma_count(i, 1);

It is spelt "weird"

Looks basically OK although it would be good to document the situation for a bus mastering DMA controller. Does the device have to reconfigure the DMA on a resume or is that something the restore code for the device should handle ?

My own feeling is tha we should dump that on the device (safer) and also expect the device to prevent suspends during an active DMA transfer (eg floppy)

Alan

#10 lore raw
Quoted 4 lines · Alan Cox

It is spelt "weird"

Ooops... :)

Quoted 11 lines

Looks basically OK although it would be good to document the situation for a bus mastering DMA controller. Does the device have to reconfigure the DMA on a resume or is that something the restore code for the device should handle ?

My own feeling is tha we should dump that on the device (safer) and also expect the device to prevent suspends during an active DMA transfer (eg floppy)

If you mean that the device drivers should restore any state then I fully agree. A central restore of the complete state of the DMA controller would require keeping a copy of all data passed to it (since some registers are write only). The reason for my piece of code here is that nobody "owns" channel 4 so it must be restored centrally. Also, resetting the other registers makes the DMA controller behave the same way on all systems (from the drivers' point of view at least).

Preventing suspend also needs to be done from the drivers. Simply because only they can determine when the devices are doing the transfer (examining the DMA controller will only tell us if it's _prepared_ for a transfer).

I'll fix the typo and whip up a patch for x86_64 then.

Rgds
Pierre

#11 lore raw

PATCH 1/2 ISA DMA suspend for i386

Reset the ISA DMA controller into a known state after a suspend. Primary concern was reenabling the cascading DMA channel (4).

arch/i386/kernel/i8237.c +67 -0
Index: linux-wbsd/arch/i386/kernel/i8237.c
===================================================================
@@ -0,0 +1,67 @@
+/*
+ * i8237.c: 8237A DMA controller suspend functions.
+ *
+ * Written by Pierre Ossman, 2005.
+ */
+
+#include <linux/init.h>
+#include <linux/sysdev.h>
+
+#include <asm/dma.h>
+
+/*
+ * This module just handles suspend/resume issues with the
+ * 8237A DMA controller (used for ISA and LPC).
+ * Allocation is handled in kernel/dma.c and normal usage is
+ * in asm/dma.h.
+ */
+
+static int i8237A_resume(struct sys_device *dev)
+{
+	unsigned long flags;
+	int i;
+
+	flags = claim_dma_lock();
+
+	dma_outb(DMA1_RESET_REG, 0);
+	dma_outb(DMA2_RESET_REG, 0);
+
+	for (i = 0;i < 8;i++) {
+		set_dma_addr(i, 0x000000);
+		/* DMA count is a bit weird so this is not 0 */
+		set_dma_count(i, 1);
+	}
+
+	/* Enable cascade DMA or channel 0-3 won't work */
+	enable_dma(4);
+
+	release_dma_lock(flags);
+
+	return 0;
+}
+
+static int i8237A_suspend(struct sys_device *dev, pm_message_t state)
+{
+	return 0;
+}
+
+static struct sysdev_class i8237_sysdev_class = {
+	set_kset_name("i8237"),
+	.suspend = i8237A_suspend,
+	.resume = i8237A_resume,
+};
+
+static struct sys_device device_i8237A = {
+	.id	= 0,
+	.cls	= &i8237_sysdev_class,
+};
+
+static int __init i8237A_init_sysfs(void)
+{
+	int error = sysdev_class_register(&i8237_sysdev_class);
+	if (!error)
+		error = sysdev_register(&device_i8237A);
+	return error;
+}
+
+device_initcall(i8237A_init_sysfs);
arch/i386/kernel/Makefile +1 -1
Index: linux-wbsd/arch/i386/kernel/Makefile
===================================================================
@@ -7,7 +7,7 @@
 obj-y	:= process.o semaphore.o signal.o entry.o traps.o irq.o vm86.o \
 		ptrace.o time.o ioport.o ldt.o setup.o i8259.o sys_i386.o \
 		pci-dma.o i386_ksyms.o i387.o dmi_scan.o bootflag.o \
-		doublefault.o quirks.o
+		doublefault.o quirks.o i8237.o
 
 obj-y				+= cpu/
 obj-y				+= timers/
#12 lore raw

PATCH 2/2 ISA DMA suspend for x86_64

In reply to Pierre Ossman

Reset the ISA DMA controller into a known state after a suspend. Primary concern was reenabling the cascading DMA channel (4).

Word of warning, I haven't tested this platform since I don't have any x86_64 hardware. But it shouldn't differ from i386.

arch/x86_64/kernel/i8237.c +67 -0
Index: linux-wbsd/arch/x86_64/kernel/i8237.c
===================================================================
@@ -0,0 +1,67 @@
+/*
+ * i8237.c: 8237A DMA controller suspend functions.
+ *
+ * Written by Pierre Ossman, 2005.
+ */
+
+#include <linux/init.h>
+#include <linux/sysdev.h>
+
+#include <asm/dma.h>
+
+/*
+ * This module just handles suspend/resume issues with the
+ * 8237A DMA controller (used for ISA and LPC).
+ * Allocation is handled in kernel/dma.c and normal usage is
+ * in asm/dma.h.
+ */
+
+static int i8237A_resume(struct sys_device *dev)
+{
+	unsigned long flags;
+	int i;
+
+	flags = claim_dma_lock();
+
+	dma_outb(DMA1_RESET_REG, 0);
+	dma_outb(DMA2_RESET_REG, 0);
+
+	for (i = 0;i < 8;i++) {
+		set_dma_addr(i, 0x000000);
+		/* DMA count is a bit weird so this is not 0 */
+		set_dma_count(i, 1);
+	}
+
+	/* Enable cascade DMA or channel 0-3 won't work */
+	enable_dma(4);
+
+	release_dma_lock(flags);
+
+	return 0;
+}
+
+static int i8237A_suspend(struct sys_device *dev, pm_message_t state)
+{
+	return 0;
+}
+
+static struct sysdev_class i8237_sysdev_class = {
+	set_kset_name("i8237"),
+	.suspend = i8237A_suspend,
+	.resume = i8237A_resume,
+};
+
+static struct sys_device device_i8237A = {
+	.id	= 0,
+	.cls	= &i8237_sysdev_class,
+};
+
+static int __init i8237A_init_sysfs(void)
+{
+	int error = sysdev_class_register(&i8237_sysdev_class);
+	if (!error)
+		error = sysdev_register(&device_i8237A);
+	return error;
+}
+
+device_initcall(i8237A_init_sysfs);
arch/x86_64/kernel/Makefile +1 -1
Index: linux-wbsd/arch/x86_64/kernel/Makefile
===================================================================
@@ -7,7 +7,7 @@
 obj-y	:= process.o semaphore.o signal.o entry.o traps.o irq.o \
 		ptrace.o time.o ioport.o ldt.o setup.o i8259.o sys_x86_64.o \
 		x8664_ksyms.o i387.o syscall.o vsyscall.o \
-		setup64.o bootflag.o e820.o reboot.o quirks.o
+		setup64.o bootflag.o e820.o reboot.o quirks.o i8237.o
 
 obj-$(CONFIG_X86_MCE)         += mce.o
 obj-$(CONFIG_X86_MCE_INTEL)	+= mce_intel.o