APM update to support 1.2 BIOS

C. Scott Ananian (cananian@lcs.mit.edu)
Thu, 12 Feb 1998 18:16:30 -0500 (EST)


Attached is a patch against kernel 2.1.86 which upgrades the kernel to
support APM version 1.2. I'd appreciate if laptop users could test this
patch out. It works for me on my APM 1.2 laptop. I'm also interested in
test results for APM 1.1 and APM 1.0 laptops (to be sure I haven't broken
anything!). APM 1.2 doesn't add a whole lot of extra functionality, and
I've deliberately *not* changed the /dev/apm, /proc/apm interfaces (2.1.X
is in code-slush) -- but it might help laptops with buggy BIOSes.

There also seems to be a bug in linux APM support where page descriptors
are corrupted by the APM bios during suspend/hibernate. I'm still
tracking this down.
--Scott
@ @
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-oOO-(_)-OOo-=-=-=-=-=
C. Scott Ananian: cananian@lcs.mit.edu / Declare the Truth boldly and
Laboratory for Computer Science/Crypto / without hindrance.
Massachusetts Institute of Technology /META-PARRESIAS AKOLUTOS:Acts 28:31
-.-. .-.. .. ..-. ..-. --- .-. -.. ... -.-. --- - - .- -. .- -. .. .- -.
PGP key available via finger and from http://www.pdos.lcs.mit.edu/~cananian

diff -ruHp -X badboys linux-2.1.86-orig/arch/i386/boot/setup.S linux/arch/i386/boot/setup.S
--- linux-2.1.86-orig/arch/i386/boot/setup.S Thu Feb 12 15:13:10 1998
+++ linux/arch/i386/boot/setup.S Thu Feb 12 15:16:01 1998
@@ -410,8 +410,8 @@ no_psmouse:
mov [68],ebx ! BIOS entry point offset
mov [72],cx ! BIOS 16 bit code segment
mov [74],dx ! BIOS data segment
- mov [78],si ! BIOS code segment length
- mov [80],di ! BIOS data segment length
+ mov [78],esi ! BIOS code segment length
+ mov [82],di ! BIOS data segment length
jmp done_apm_bios

no_32_apm_bios:
diff -ruHp -X badboys linux-2.1.86-orig/drivers/char/apm_bios.c linux/drivers/char/apm_bios.c
--- linux-2.1.86-orig/drivers/char/apm_bios.c Thu Feb 12 15:11:56 1998
+++ linux/drivers/char/apm_bios.c Thu Feb 12 17:40:00 1998
@@ -141,6 +141,8 @@ extern unsigned long get_cmos_time(void)
* U: ACER 486DX4/75: uses dseg 0040, in violation of APM specification
* [Confirmed by BIOS disassembly]
* P: Toshiba 1950S: battery life information only gets updated after resume
+ * P: Midwest Micro Soundbook Elite DX2/66 monochrome: screen blanking
+ * broken in BIOS [Reported by Garst R. Reese <reese@isn.net>]
*
* Legend: U = unusable with APM patches
* P = partially usable with APM patches
@@ -279,9 +281,15 @@ extern unsigned long get_cmos_time(void)
: "a" (0x530a), "b" (1) \
APM_BIOS_CALL_END

-#define APM_GET_EVENT(event, error) \
+#define APM_GET_BATTERY_STATUS(which, bx, cx, dx, si, error) \
APM_BIOS_CALL(al) \
- : "=a" (error), "=b" (event) \
+ : "=a" (error), "=b" (bx), "=c" (cx), "=d" (dx), "=S" (si) \
+ : "a" (0x530a), "b" (0x8000 | (which)) \
+ APM_BIOS_CALL_END
+
+#define APM_GET_EVENT(event, info, error) \
+ APM_BIOS_CALL(al) \
+ : "=a" (error), "=b" (event), "=c" (info) \
: "a" (0x530b) \
APM_BIOS_CALL_END

@@ -356,7 +364,8 @@ static char * apm_event_name[] = {
"critical suspend",
"user standby",
"user suspend",
- "system standby resume"
+ "system standby resume",
+ "capabilities change"
};
#define NR_APM_EVENT_NAME \
(sizeof(apm_event_name) / sizeof(apm_event_name[0]))
@@ -406,6 +415,8 @@ static const lookup_t error_table[] = {
{ APM_BAD_DEVICE, "Unrecognized device ID" },
{ APM_BAD_PARAM, "Parameter out of range" },
{ APM_NOT_ENGAGED, "Interface not engaged" },
+ { APM_BAD_FUNCTION, "Function not supported" },
+ { APM_RESUME_DISABLED, "Resume timer disabled" },
{ APM_BAD_STATE, "Unable to enter requested state" },
/* N/A { APM_NO_EVENTS, "No events pending" }, */
{ APM_NOT_PRESENT, "No APM present" }
@@ -423,13 +434,15 @@ static int apm_driver_version(u_short *v
return APM_SUCCESS;
}

-static int apm_get_event(apm_event_t *event)
+static int apm_get_event(apm_event_t *event, apm_eventinfo_t *info)
{
u_short error;

- APM_GET_EVENT(*event, error);
+ APM_GET_EVENT(*event, *info, error);
if (error & 0xff)
return (error >> 8);
+ if (apm_bios_info.version < 0x0102)
+ *info = ~0; /* indicate info not valid */
return APM_SUCCESS;
}

@@ -481,6 +494,24 @@ static int apm_get_power_status(u_short
return APM_SUCCESS;
}

+static int apm_get_battery_status(u_short which,
+ u_short *bat, u_short *life, u_short *nbat)
+{
+ u_short status, error;
+
+ if (apm_bios_info.version < 0x0102) {
+ /* pretend we only have one battery. */
+ if (which!=1) return APM_BAD_DEVICE;
+ *nbat = 1;
+ return apm_get_power_status(&status, bat, life);
+ }
+
+ APM_GET_BATTERY_STATUS(which, status, *bat, *life, *nbat, error);
+ if (error & 0xff)
+ return (error >> 8);
+ return APM_SUCCESS;
+}
+
static inline int apm_engage_power_management(u_short device)
{
u_short error;
@@ -652,10 +683,12 @@ static apm_event_t get_event(void)
{
int error;
apm_event_t event;
+ apm_eventinfo_t info;

static int notified = 0;

- error = apm_get_event(&event);
+ /* we don't use the eventinfo */
+ error = apm_get_event(&event, &info);
if (error == APM_SUCCESS)
return event;

@@ -718,6 +751,7 @@ static void check_events(void)

case APM_LOW_BATTERY:
case APM_POWER_STATUS_CHANGE:
+ case APM_CAPABILITY_CHANGE:
send_event(event, 0, NULL);
break;

@@ -1106,12 +1140,17 @@ __initfunc(void apm_bios_init(void))
if (apm_bios_info.version == 0x001)
apm_bios_info.version = 0x100;

+ /* BIOS < 1.2 doesn't set cseg_16_len */
+ if (apm_bios_info.version < 0x102)
+ apm_bios_info.cseg_16_len = 0xFFFF; /* 64k */
+
printk(KERN_INFO " Entry %x:%lx cseg16 %x dseg %x",
apm_bios_info.cseg, apm_bios_info.offset,
apm_bios_info.cseg_16, apm_bios_info.dseg);
if (apm_bios_info.version > 0x100)
- printk(" cseg len %x, dseg len %x",
- apm_bios_info.cseg_len, apm_bios_info.dseg_len);
+ printk(" cseg len %x, cseg16 len %x, dseg len %x",
+ apm_bios_info.cseg_len, apm_bios_info.cseg_16_len,
+ apm_bios_info.dseg_len);
printk("\n");

/*
@@ -1146,19 +1185,25 @@ __initfunc(void apm_bios_init(void))
set_limit(gdt[APM_DS >> 3], 64 * 1024);
#else
set_limit(gdt[APM_CS >> 3], apm_bios_info.cseg_len);
- set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
+ set_limit(gdt[APM_CS_16 >> 3], apm_bios_info.cseg_16_len);
set_limit(gdt[APM_DS >> 3], apm_bios_info.dseg_len);
#endif
- apm_bios_info.version = 0x0101;
+ /* The APM 1.2 docs state that the apm_driver_version
+ * call can fail if we try to connect as 1.2 to a 1.1 bios.
+ */
+ apm_bios_info.version = 0x0102;
error = apm_driver_version(&apm_bios_info.version);
- if (error != 0)
+ if (error != 0) { /* Fall back to an APM 1.1 connection. */
+ apm_bios_info.version = 0x0101;
+ error = apm_driver_version(&apm_bios_info.version);
+ }
+ if (error != 0) /* Fall back to an APM 1.0 connection. */
apm_bios_info.version = 0x100;
else {
apm_engage_power_management(0x0001);
printk( " Connection version %d.%d\n",
(apm_bios_info.version >> 8) & 0xff,
apm_bios_info.version & 0xff );
- apm_bios_info.version = 0x0101;
}
}

diff -ruHp -X badboys linux-2.1.86-orig/include/linux/apm_bios.h linux/include/linux/apm_bios.h
--- linux-2.1.86-orig/include/linux/apm_bios.h Thu Feb 12 15:09:48 1998
+++ linux/include/linux/apm_bios.h Thu Feb 12 16:32:16 1998
@@ -19,6 +19,7 @@
*/

typedef unsigned short apm_event_t;
+typedef unsigned short apm_eventinfo_t;

#ifdef __KERNEL__

@@ -35,6 +36,7 @@ struct apm_bios_info {
unsigned short dseg;
unsigned short flags;
unsigned short cseg_len;
+ unsigned short cseg_16_len;
unsigned short dseg_len;
};

@@ -111,6 +113,7 @@ extern int apm_display_unblank(void);
#define APM_USER_STANDBY 0x0009
#define APM_USER_SUSPEND 0x000a
#define APM_STANDBY_RESUME 0x000b
+#define APM_CAPABILITY_CHANGE 0x000c

/*
* Error codes
@@ -126,6 +129,8 @@ extern int apm_display_unblank(void);
#define APM_BAD_DEVICE 0x09
#define APM_BAD_PARAM 0x0a
#define APM_NOT_ENGAGED 0x0b
+#define APM_BAD_FUNCTION 0x0c
+#define APM_RESUME_DISABLED 0x0d
#define APM_BAD_STATE 0x60
#define APM_NO_EVENTS 0x80
#define APM_NOT_PRESENT 0x86

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu