[PATCH] Longhaul - call suspend(PMSG_FREEZE) before and resume()after

From: Rafał Bilski
Date: Sat May 27 2006 - 18:30:19 EST


I posted this to Dave Jones, but:
> This really should also get posted to linux-kernel, though I don't
> think people are going to be particularly enthusiastic about exposing
> these innards to modules.
So I'm posting this patch here too. I'm not subscribed to this list.
If You wish email me.
Minor change: preempt_disabled() goto_sleep_pci() and wakeup_pci()
causes "sheduling while atomic". So in this patch below it isn't
preempt_disabled(). But I think that this should be protected
in some way.

How it works:
1. Call suspend(PMSG_FREEZE) for each block device.
2. Call suspend(PMSG_FREEZE) for each PCI device.
3. Change CPU frequency.
4. Call resume() for each PCI device.
5. Call resume() for each block device.

Result: No more broken DMA transfers caused by frequency change.

diff -uprN -X linux-2.6.16.18-vanilla/Documentation/dontdiff linux-2.6.16.18-vanilla/arch/i386/kernel/cpu/cpufreq/Kconfig linux-2.6.16.18/arch/i386/kernel/cpu/cpufreq/Kconfig
--- linux-2.6.16.18-vanilla/arch/i386/kernel/cpu/cpufreq/Kconfig 2006-05-27 15:50:00.000000000 +0200
+++ linux-2.6.16.18/arch/i386/kernel/cpu/cpufreq/Kconfig 2006-05-27 15:53:13.000000000 +0200
@@ -203,7 +203,6 @@ config X86_LONGRUN
config X86_LONGHAUL
tristate "VIA Cyrix III Longhaul"
select CPU_FREQ_TABLE
- depends on BROKEN
help
This adds the CPUFreq driver for VIA Samuel/CyrixIII,
VIA Cyrix Samuel/C3, VIA Cyrix Ezra and VIA Cyrix Ezra-T
diff -uprN -X linux-2.6.16.18-vanilla/Documentation/dontdiff linux-2.6.16.18-vanilla/arch/i386/kernel/cpu/cpufreq/longhaul.c linux-2.6.16.18/arch/i386/kernel/cpu/cpufreq/longhaul.c
--- linux-2.6.16.18-vanilla/arch/i386/kernel/cpu/cpufreq/longhaul.c 2006-03-20 06:53:29.000000000 +0100
+++ linux-2.6.16.18/arch/i386/kernel/cpu/cpufreq/longhaul.c 2006-05-27 15:53:13.000000000 +0200
@@ -30,6 +30,11 @@
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/pci.h>
+#include <linux/pm.h>
+#include <linux/kobject.h>
+#include <linux/sysdev.h>
+#include <linux/genhd.h>
+#include <linux/blkdev.h>

#include <asm/msr.h>
#include <asm/timex.h>
@@ -115,16 +120,144 @@ static int longhaul_get_cpu_mult(void)
}


-static void do_powersaver(union msr_longhaul *longhaul,
- unsigned int clock_ratio_index)
+extern struct subsystem block_subsys;
+extern struct semaphore block_subsys_sem;
+
+static struct pm_message pmsg_freeze = { .event = PM_EVENT_FREEZE, };
+
+void block_freeze_resume(int freeze)
+{
+ struct gendisk *disk;
+ struct list_head *p;
+ struct bus_type *bus;
+
+ list_for_each(p, &block_subsys.kset.list) {
+ disk = list_entry(p, struct gendisk, kobj.entry);
+ if (disk->driverfs_dev && disk->driverfs_dev->bus) {
+ bus = disk->driverfs_dev->bus;
+ if (bus->suspend && freeze) {
+ printk("Longhaul: %.32s (%.32s) -> suspend(PMSG_FREEZE)\n", &disk->disk_name[0], &disk->driverfs_dev->bus_id[0]);
+ bus->suspend(disk->driverfs_dev, pmsg_freeze);
+ } else if (bus->resume && !freeze) {
+ printk("Longhaul: %.32s (%.32s) -> resume()\n", &disk->disk_name[0], &disk->driverfs_dev->bus_id[0]);
+ bus->resume(disk->driverfs_dev);
+ }
+ }
+ }
+}
+
+
+/* This is copy from pci.c and pci-driver.c
+ */
+
+static void goto_sleep_pci(void)
+{
+ struct pci_dev *dev;
+ struct pci_driver *drv;
+ u16 pci_command;
+
+ /*
+ * Get current pci bus master state for all devices
+ * and clear bus master bit
+ */
+
+ dev = NULL;
+ do {
+ dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev);
+ if (dev != NULL) {
+ drv = dev->driver;
+ if (drv && drv->suspend)
+ drv->suspend(dev, pmsg_freeze);
+ else
+ pci_save_state(dev);
+ pci_read_config_word(dev, PCI_COMMAND, &pci_command);
+ if (pci_command & PCI_COMMAND_MASTER) {
+ pci_command &= ~PCI_COMMAND_MASTER;
+ pci_write_config_word(dev, PCI_COMMAND, pci_command);
+ }
+ /* pcibios_disable_device omited because
+ USB devices don't respond after resume
+ on EPIA M10000 */
+ }
+ } while (dev != NULL);
+}
+
+
+static void wakeup_pci(void)
{
struct pci_dev *dev;
+ struct pci_driver *drv;
+
+ /* Restore pci bus master state for all devices */
+
+ dev = NULL;
+ do {
+ dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev);
+ if (dev != NULL) {
+ drv = dev->driver;
+ if (drv && drv->resume)
+ drv->resume(dev);
+ else
+ /* restore the PCI config space */
+ pci_restore_state(dev);
+ /* if the device was busmaster before the suspend, make it busmaster again */
+ if (dev->is_busmaster)
+ pci_set_master(dev);
+ /* if the device was enabled before suspend, reenable */
+ if (dev->is_enabled)
+ pci_enable_device(dev);
+ }
+ } while (dev != NULL);
+}
+
+
+static void do_longhaul1(int reserved, unsigned int clock_ratio_index)
+{
unsigned long flags;
unsigned int tmp_mask;
+ union msr_bcr2 bcr2;
+
+ down(&block_subsys_sem);
+ block_freeze_resume(1);
+ goto_sleep_pci();
+ preempt_disable();
+ local_irq_save(flags);
+
+ local_irq_disable();
+
+ tmp_mask=inb(0x21); /* works on C3. save mask. */
+ outb(0xFE, 0x21); /* TMR0 only */
+
+ rdmsrl(MSR_VIA_BCR2, bcr2.val);
+ /* Enable software clock multiplier */
+ bcr2.bits.ESOFTBF = 1;
+ bcr2.bits.CLOCKMUL = clock_ratio_index;
+ safe_halt(); /* Sync */
+ wrmsrl(MSR_VIA_BCR2, bcr2.val); /* Change */
+ halt();
+
+ /* Disable software clock multiplier */
+ local_irq_disable();
+ rdmsrl(MSR_VIA_BCR2, bcr2.val);
+ bcr2.bits.ESOFTBF = 0;
+ wrmsrl(MSR_VIA_BCR2, bcr2.val);
+ local_irq_enable();
+
+ outb(tmp_mask, 0x21); /* restore mask */
+ local_irq_restore(flags);
+ preempt_enable();
+ wakeup_pci();
+ block_freeze_resume(0);
+ up(&block_subsys_sem);
+}
+
+
+static void do_powersaver(union msr_longhaul *longhaul,
+ unsigned int clock_ratio_index)
+{
+ unsigned long flags;
+ unsigned int pic1_mask, pic2_mask;
int version;
- int i;
- u16 pci_cmd;
- u16 cmd_state[64];

switch (cpu_model) {
case CPU_EZRA_T:
@@ -137,61 +270,44 @@ static void do_powersaver(union msr_long
return;
}

+ down(&block_subsys_sem);
+ block_freeze_resume(1);
+ goto_sleep_pci();
+ preempt_disable();
+ local_irq_save(flags);
+
+ local_irq_disable();
+ pic2_mask=inb(0xA1);
+ pic1_mask=inb(0x21); /* works on C3. save mask. */
+ outb(0xFF,0xA1); /* Overkill */
+ outb(0xFE,0x21); /* TMR0 only */
+
rdmsrl(MSR_VIA_LONGHAUL, longhaul->val);
longhaul->bits.SoftBusRatio = clock_ratio_index & 0xf;
longhaul->bits.SoftBusRatio4 = (clock_ratio_index & 0x10) >> 4;
longhaul->bits.EnableSoftBusRatio = 1;
longhaul->bits.RevisionKey = 0;

- preempt_disable();
- local_irq_save(flags);
-
- /*
- * get current pci bus master state for all devices
- * and clear bus master bit
- */
- dev = NULL;
- i = 0;
- do {
- dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev);
- if (dev != NULL) {
- pci_read_config_word(dev, PCI_COMMAND, &pci_cmd);
- cmd_state[i++] = pci_cmd;
- pci_cmd &= ~PCI_COMMAND_MASTER;
- pci_write_config_word(dev, PCI_COMMAND, pci_cmd);
- }
- } while (dev != NULL);
-
- tmp_mask=inb(0x21); /* works on C3. save mask. */
- outb(0xFE,0x21); /* TMR0 only */
- outb(0xFF,0x80); /* delay */
-
- safe_halt();
+ safe_halt(); /* Sync */
wrmsrl(MSR_VIA_LONGHAUL, longhaul->val);
halt();

local_irq_disable();

- outb(tmp_mask,0x21); /* restore mask */
-
- /* restore pci bus master state for all devices */
- dev = NULL;
- i = 0;
- do {
- dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev);
- if (dev != NULL) {
- pci_cmd = cmd_state[i++];
- pci_write_config_byte(dev, PCI_COMMAND, pci_cmd);
- }
- } while (dev != NULL);
- local_irq_restore(flags);
- preempt_enable();
-
/* disable bus ratio bit */
rdmsrl(MSR_VIA_LONGHAUL, longhaul->val);
longhaul->bits.EnableSoftBusRatio = 0;
longhaul->bits.RevisionKey = version;
wrmsrl(MSR_VIA_LONGHAUL, longhaul->val);
+
+ outb(pic2_mask,0xA1);
+ outb(pic1_mask,0x21); /* restore mask */
+
+ local_irq_restore(flags);
+ preempt_enable();
+ wakeup_pci();
+ block_freeze_resume(0);
+ up(&block_subsys_sem);
}

/**
@@ -206,7 +322,6 @@ static void longhaul_setstate(unsigned i
int speed, mult;
struct cpufreq_freqs freqs;
union msr_longhaul longhaul;
- union msr_bcr2 bcr2;
static unsigned int old_ratio=-1;

if (old_ratio == clock_ratio_index)
@@ -241,20 +356,7 @@ static void longhaul_setstate(unsigned i
*/
case TYPE_LONGHAUL_V1:
case TYPE_LONGHAUL_V2:
- rdmsrl (MSR_VIA_BCR2, bcr2.val);
- /* Enable software clock multiplier */
- bcr2.bits.ESOFTBF = 1;
- bcr2.bits.CLOCKMUL = clock_ratio_index;
- local_irq_disable();
- wrmsrl (MSR_VIA_BCR2, bcr2.val);
- safe_halt();
-
- /* Disable software clock multiplier */
- rdmsrl (MSR_VIA_BCR2, bcr2.val);
- bcr2.bits.ESOFTBF = 0;
- local_irq_disable();
- wrmsrl (MSR_VIA_BCR2, bcr2.val);
- local_irq_enable();
+ do_longhaul1(0, clock_ratio_index);
break;

/*
diff -uprN -X linux-2.6.16.18-vanilla/Documentation/dontdiff linux-2.6.16.18-vanilla/block/genhd.c linux-2.6.16.18/block/genhd.c
--- linux-2.6.16.18-vanilla/block/genhd.c 2006-05-27 15:50:01.000000000 +0200
+++ linux-2.6.16.18/block/genhd.c 2006-05-27 15:53:13.000000000 +0200
@@ -16,9 +16,9 @@
#include <linux/kobj_map.h>
#include <linux/buffer_head.h>

-static struct subsystem block_subsys;
+struct subsystem block_subsys;

-static DECLARE_MUTEX(block_subsys_sem);
+DECLARE_MUTEX(block_subsys_sem);

/*
* Can be deleted altogether. Later.
@@ -511,8 +511,10 @@ static struct kset_uevent_ops block_ueve
};

/* declare block_subsys. */
-static decl_subsys(block, &ktype_block, &block_uevent_ops);
+decl_subsys(block, &ktype_block, &block_uevent_ops);

+EXPORT_SYMBOL(block_subsys);
+EXPORT_SYMBOL(block_subsys_sem);

/*
* aggregate disk stat collector. Uses the same stats that the sysfs




----------------------------------------------------------------------
Tysiace zdjec swietnych samochodow! >>> http://link.interia.pl/f1944

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/