patch-2.3.20 linux/drivers/misc/acpi.c

Next file: linux/drivers/misc/piix4_acpi.c
Previous file: linux/drivers/misc/Makefile
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.19/linux/drivers/misc/acpi.c linux/drivers/misc/acpi.c
@@ -18,6 +18,11 @@
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+/*
+ * See http://www.geocities.com/SiliconValley/Hardware/3165/
+ * for the user-level ACPI stuff
+ */
+
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/miscdevice.h>
@@ -46,6 +51,10 @@
 static struct acpi_facp *acpi_facp = NULL;
 static unsigned long acpi_facp_addr = 0;
 static unsigned long acpi_dsdt_addr = 0;
+
+static volatile u32 acpi_pm1_status = 0;
+static volatile u32 acpi_gpe_status = 0;
+static volatile u32 acpi_gpe_level = 0;
 static DECLARE_WAIT_QUEUE_HEAD(acpi_wait_event);
 
 /*
@@ -73,6 +82,19 @@
 }
 
 /*
+ * Get the value of the fixed event enable register
+ */
+static u32 acpi_read_pm1_enable(struct acpi_facp *facp)
+{
+	int offset = facp->pm1_evt_len >> 1;
+	u32 value = inw(facp->pm1a_evt + offset);
+	if (facp->pm1b_evt) {
+		value |= inw(facp->pm1b_evt + offset);
+	}
+	return value;
+}
+
+/*
  * Set the value of the fixed event enable register (enable events)
  */
 static void acpi_write_pm1_enable(struct acpi_facp *facp, u32 value)
@@ -128,6 +150,28 @@
 }
 
 /*
+ * Get the value of the general-purpose event enable register
+ */
+static u32 acpi_read_gpe_enable(struct acpi_facp *facp)
+{
+	u32 value = 0;
+	int i, size, offset;
+	
+	offset = facp->gpe0_len >> 1;
+	if (facp->gpe1) {
+		size = facp->gpe1_len >> 1;
+		for (i = size - 1; i >= 0; i--) {
+			value = (value << 8) | inb(facp->gpe1 + offset + i);
+		}
+	}
+	size = facp->gpe0_len >> 1;
+	for (i = size - 1; i >= 0; i--) {
+		value = (value << 8) | inb(facp->gpe0 + offset + i);
+	}
+	return value;
+}
+
+/*
  * Set the value of the general-purpose event enable register (enable events)
  */
 static void acpi_write_gpe_enable(struct acpi_facp *facp, u32 value)
@@ -157,7 +201,8 @@
 	if (addr) {
 		// map table header to determine size
 		table = (struct acpi_table *)
-		    ioremap_nocache((unsigned long) addr, sizeof(struct acpi_table));
+		    ioremap_nocache((unsigned long) addr,
+				    sizeof(struct acpi_table));
 		if (table) {
 			unsigned long table_size = table->length;
 			iounmap(table);
@@ -201,10 +246,13 @@
 			// strip trailing space and print OEM identifier
 			memcpy_fromio(oem, rsdp->oem, 6);
 			oem[6] = '\0';
-			for (j = 5; j > 0 && (oem[j] == '\0' || oem[j] == ' '); j--) {
+			for (j = 5;
+			     j > 0 && (oem[j] == '\0' || oem[j] == ' ');
+			     j--) {
 				oem[j] = '\0';
 			}
-			printk(KERN_INFO "ACPI: \"%s\" found at 0x%p\n", oem, (void *) i);
+			printk(KERN_INFO "ACPI: \"%s\" found at 0x%p\n",
+			       oem, (void *) i);
 
 			break;
 		}
@@ -252,6 +300,7 @@
  */
 static void acpi_unmap_tables(void)
 {
+	acpi_idle = NULL;
 	acpi_dsdt_addr = 0;
 	acpi_facp_addr = 0;
 	acpi_unmap_table((struct acpi_table *) acpi_facp);
@@ -263,17 +312,32 @@
  */
 static void acpi_irq(int irq, void *dev_id, struct pt_regs *regs)
 {
-	u32 status;
-
-	// detect and disable any fixed events
-	status = acpi_read_pm1_status(acpi_facp);
-	acpi_write_pm1_enable(acpi_facp, ~status);
-
-	// detect and disable any general-purpose events
-	status = acpi_read_gpe_status(acpi_facp);
-	acpi_write_gpe_enable(acpi_facp, ~status);
-
+	u32 pm1_status, gpe_status, gpe_level, gpe_edge;
+	// detect and clear fixed events
+	pm1_status = (acpi_read_pm1_status(acpi_facp)
+		      & acpi_read_pm1_enable(acpi_facp));
+	acpi_write_pm1_status(acpi_facp, pm1_status);
+	
+	// detect and handle general-purpose events
+	gpe_status = (acpi_read_gpe_status(acpi_facp)
+		      & acpi_read_gpe_enable(acpi_facp));
+	gpe_level = gpe_status & acpi_gpe_level;
+	if (gpe_level) {
+		// disable level-triggered events
+		acpi_write_gpe_enable(
+			acpi_facp,
+			acpi_read_gpe_enable(acpi_facp) & ~gpe_level);
+	}
+	gpe_edge = gpe_status & ~gpe_level;
+	if (gpe_edge) {
+		// clear edge-triggered events
+		while (acpi_read_gpe_status(acpi_facp) & gpe_edge)
+			acpi_write_gpe_status(acpi_facp, gpe_edge);
+	}
+	
 	// notify process reading /dev/acpi
+	acpi_pm1_status |= pm1_status;
+	acpi_gpe_status |= gpe_status;
 	wake_up_interruptible(&acpi_wait_event);
 }
 
@@ -311,17 +375,79 @@
 				     (void *) arg,
 				     sizeof(struct acpi_find_tables));
 		if (!status) {
-			struct acpi_find_tables *rqst = (struct acpi_find_tables *) arg;
+			struct acpi_find_tables *rqst
+				= (struct acpi_find_tables *) arg;
 			put_user(acpi_facp_addr, &rqst->facp);
 			put_user(acpi_dsdt_addr, &rqst->dsdt);
 			status = 0;
 		}
 		break;
+	case ACPI_ENABLE_EVENT:
+		status = verify_area(VERIFY_READ,
+				     (void *) arg,
+				     sizeof(struct acpi_enable_event));
+		if (!status) {
+			struct acpi_enable_event *rqst
+				= (struct acpi_enable_event *) arg;
+			u32 pm1_enable, gpe_enable, gpe_level;
+			u32 pm1_enabling, gpe_enabling;
+			
+			get_user(pm1_enable, &rqst->pm1_enable);
+			get_user(gpe_enable, &rqst->gpe_enable);
+			get_user(gpe_level, &rqst->gpe_level);
+			gpe_level &= gpe_enable;
+			
+			// clear previously disabled events before enabling
+			pm1_enabling = (pm1_enable
+					& ~acpi_read_pm1_enable(acpi_facp));
+			acpi_write_pm1_status(acpi_facp, pm1_enabling);
+			gpe_enabling = (gpe_enable &
+					~acpi_read_gpe_enable(acpi_facp));
+			while (acpi_read_gpe_status(acpi_facp) & gpe_enabling)
+				acpi_write_gpe_status(acpi_facp, gpe_enabling);
+			
+			acpi_write_pm1_enable(acpi_facp, pm1_enable);
+			acpi_write_gpe_enable(acpi_facp, gpe_enable);
+			acpi_gpe_level = gpe_level;
+			
+			status = 0;
+		}
+		break;
 	case ACPI_WAIT_EVENT:
-		interruptible_sleep_on(&acpi_wait_event);
-		if (signal_pending(current))
-			return -ERESTARTSYS;
-		status = 0;
+		status = verify_area(VERIFY_WRITE,
+				     (void *) arg,
+				     sizeof(struct acpi_wait_event));
+		if (!status) {
+			struct acpi_wait_event *rqst
+				= (struct acpi_wait_event *) arg;
+			u32 pm1_status = 0;
+			u32 gpe_status = 0;
+			
+			for (;;) {
+				unsigned long flags;
+				
+				// we need an atomic exchange here
+				save_flags(flags);
+				cli();
+				pm1_status = acpi_pm1_status;
+				acpi_pm1_status = 0;
+				gpe_status = acpi_gpe_status;
+				acpi_gpe_status = 0;
+				restore_flags(flags);
+
+				if (pm1_status || gpe_status)
+					break;
+
+				// wait for an event to arrive
+				interruptible_sleep_on(&acpi_wait_event);
+				if (signal_pending(current))
+					return -ERESTARTSYS;
+			}
+
+			put_user(pm1_status, &rqst->pm1_status);
+			put_user(gpe_status, &rqst->gpe_status);
+			status = 0;
+		}
 		break;
 	}
 	return status;
@@ -355,6 +481,48 @@
 	NULL
 };
 
+/* Make it impossible to enter L2/L3 until after we've initialized */
+static unsigned long acpi_p_lvl2_lat = ~0UL;
+static unsigned long acpi_p_lvl3_lat = ~0UL;
+
+/* Initialize to guaranteed harmless port read */
+static u16 acpi_p_lvl2 = 0x80;
+static u16 acpi_p_lvl3 = 0x80;
+
+static void acpi_idle_handler(void)
+{
+	unsigned long time;
+	static int sleep_level = 1;
+
+	time = inl(acpi_facp->pm_tmr);
+	switch (sleep_level) {
+	case 1:
+		__asm__ __volatile__("sti ; hlt": : :"memory");
+		break;
+	case 2:
+		inb(acpi_p_lvl2);
+		break;
+	case 3:
+		/* Disable PCI arbitration while sleeping, to avoid DMA corruption? */
+		if (acpi_facp->pm2_cnt) {
+			unsigned int port = acpi_facp->pm2_cnt;
+			outb(inb(port) | ACPI_ARB_DIS, port);
+			inb(acpi_p_lvl3);
+			outb(inb(port) & ~ACPI_ARB_DIS, port);
+			break;
+		}
+		inb(acpi_p_lvl3);
+	}
+	time = (inl(acpi_facp->pm_tmr) - time) & ACPI_TMR_MASK;
+
+	if (time > acpi_p_lvl3_lat)
+		sleep_level = 3;
+	else if (time > acpi_p_lvl2_lat)
+		sleep_level = 2;
+	else
+		sleep_level = 1;
+}
+
 /*
  * Initialize and enable ACPI
  */
@@ -376,6 +544,17 @@
 	if (misc_register(&acpi_device)) {
 		printk(KERN_ERR "ACPI: misc. register failed\n");
 	}
+
+	/*
+	 * Set up the ACPI idle function. Note that we can't really
+	 * do this with multiple CPU's, we'd need a per-CPU ACPI
+	 * device..
+	 */
+#ifdef __SMP__
+	if (smp_num_cpus > 1)
+		return 0;
+#endif
+	acpi_idle = acpi_idle_handler;
 	return 0;
 }
 
@@ -389,7 +568,8 @@
 	// disable and clear any pending events
 	acpi_write_gpe_enable(acpi_facp, 0);
 	while (acpi_read_gpe_status(acpi_facp)) {
-		acpi_write_gpe_status(acpi_facp, acpi_read_gpe_status(acpi_facp));
+		acpi_write_gpe_status(acpi_facp,
+				      acpi_read_gpe_status(acpi_facp));
 	}
 	acpi_write_pm1_enable(acpi_facp, 0);
 	acpi_write_pm1_status(acpi_facp, acpi_read_pm1_status(acpi_facp));
@@ -405,6 +585,7 @@
 
 module_init(acpi_init)
 module_exit(acpi_exit)
+
 #else
 
 __initcall(acpi_init);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)