[source] apm.c: just get battery status

I’ve wanted to just glean the battery status of the APM laptop,
so here’s a little tool to just query the APM BIOS about it.

The next step is to turn this into a resource manager and
attatch permanently as an “APM Driver”, but this needs a
public API to provide for i.e device drivers to i.e send pulses
on Sleep event.
Is there any QSSL under-development APIs for power management to follow?

References: Advanced Power Management v1.2
URL:http://www.microsoft.com/hwdev/archive/BUSBIOS/amp_12.asp

kabe

/*

  • apm.c
  • for QNX RTP(x86)
    /
    #if QNXNTO && X86
    /
    Ok, QNXRTP on x86 platform */
    #else
    #error Only for x86 architecture.
    #endif

#include <stdio.h>
#include <sys/types.h> /u_short/
#include <x86/v86.h>

/NetBSD-ish defines/
/APM/
#define APM_SYSTEM_BIOS 0x15 /int 15h/
#define APM_DEV_APM_BIOS 0x0000 /for BX in int 15h BIOS call/
#define APM_INSTALLATION_CHECK 0x5300 /for AX/
#define APM_32BIT_CONNECT 0x5303
#define APM_DISCONNECT 0x5304
#define APM_GET_POWER_STATUS 0x530a
#define APM_DRIVER_VERSION 0x530e /set version of this driver/
#define APM_GET_CAPABILITIES 0x5310
struct {
u_short apm_flags;
u_short apm_version; /* BCD xx.xx /
u_short apm_driver_version; /
BCD xx.xx */
} apm_info;

/* for struct _v86reg /
#define PSL_C 1 /
carry flag mask */

#define BH(reg) ((((reg).ebx)>>8)&0xff)
#define BL(reg) (((reg).ebx)&0xff)
#define CH(reg) ((((reg).ecx)>>8)&0xff)
#define CL(reg) (((reg).ecx)&0xff)


#define DPRINTF(x) printf x
/*#define DUMPREGS(x) dumpregs(x)//
#define DUMPREGS(x) /
/

void
dumpregs(const struct _v86reg *reg)
{
DPRINTF((“AX=%04X BX=%04X CX=%04X DX=%04X\n”,
(int)reg->eax, (int)reg->ebx, (int)reg->ecx, (int)reg->edx));
DPRINTF((“FL=%04X ESI=%08lX\n”, (int)reg->efl, reg->esi));
}

int
main()
{
struct _v86reg reg;
int s;

/* installation check /
DPRINTF((“APM: Installation Check\n”));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_INSTALLATION_CHECK; /APM Installation Check/
reg.ebx = APM_DEV_APM_BIOS; /APM BIOS/
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s || reg.efl&PSL_C) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
DUMPREGS(&reg);/XXX/
/

  • AH.AL: APM version number (BCD)
  • BH.BL: “PM”
  • CX: 0 has16bit
  • 1 has32bit
    
  • 2 Idle slows CPU
    
  • 3 disable
    
  • 4 disengage
    

*/
DPRINTF((" APM Supported: %s\n", (reg.efl&PSL_C)?“no”:“yes”));
apm_info.apm_version = reg.eax;
apm_info.apm_flags = reg.ecx;
DPRINTF((" BX signature=<%c%c>%s\n",
BH(reg), BL(reg),
((reg.ebx&0xffff)==(‘P’<<8)+‘M’)?" (ok)":" (MISMATCH)"));
DPRINTF((" BIOS APM version %x.%x\n",
(apm_info.apm_version>>8)&255, apm_info.apm_version&255 ));
if(apm_info.apm_flags & 1) { DPRINTF((" 16bit mode interface\n")); }
if(apm_info.apm_flags & 2) { DPRINTF((" 32bit mode interface\n")); }
if(apm_info.apm_flags & 4) { DPRINTF((" Slow CPU on Idle\n")); }
DPRINTF((" BIOS PM %s\n", (apm_info.apm_flags&8)?“disabled”:“enabled”));
DPRINTF((" BIOS PM %s\n", (apm_info.apm_flags&16)?“disengaged”:“engaged”));

/* disconnect first, in case somebody was connected */
memset(&reg, 0, sizeof(reg));
reg.eax = APM_DISCONNECT;
reg.ebx = APM_DEV_APM_BIOS; /APM BIOS/
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
/if (reg.efl&PSL_C) { fprintf(stderr, “APM: disconnect failed\n”); exit(1); }/

/* connect 32 /
DPRINTF((“APM: Connect32\n”));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_32BIT_CONNECT;
reg.ebx = APM_DEV_APM_BIOS; /APM BIOS/
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
if (reg.efl&PSL_C) {
fprintf(stderr, “APM: Connect32 failed (code 0x%02X)\n”, reg.eax);
return 1;
}
DUMPREGS(&reg);
/

  • AX: code32 realmode CS
  • (E)BX: entry point offset
  • CX: code16 realmode CS
  • ESI: 16 code32 length-1
  •  16 code16 length-1
    
  • DX: data realmode DS
  • DI: data length
    */
    DPRINTF((" 32Bit entry=%04X:%04X (len %04X+1)\n", reg.eax, reg.ebx, reg.esi&0xffff));
    DPRINTF((" 16Bit entry=%04X:%04X (len %04X+1)\n", reg.ecx, reg.ebx, (reg.esi>>16)&0xffff));
    DPRINTF((" Data region=%04X:( 0) (len %04X+1)\n", reg.edx, reg.edi));

/* set my version (needed to access 1.1- BIOS calls) /
apm_info.apm_driver_version = 0x0101; /
APM 1.1 /
DPRINTF((“APM: set Driver Version to %x.%x\n”,
(apm_info.apm_driver_version>>8)&255, apm_info.apm_driver_version&255
));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_DRIVER_VERSION;
reg.ebx = APM_DEV_APM_BIOS;
reg.ecx = apm_info.apm_driver_version;
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
/

  • AH.AL: APM connection version
    */
    if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
    if (reg.efl&PSL_C) {
    fprintf(stderr, “APM: set Driver Version failed (code 0x%02X)\n”, reg.eax>>8);
    return 1;
    }
    DPRINTF((" Connected as version %x.%x\n", (reg.eax>>8)&255, reg.eax&255 ));

/* get capabilities (1.2) /
#if 1 /
{/
DPRINTF((“APM: Get capabilities\n”));
if (apm_info.apm_version < 0x0102 ||
apm_info.apm_driver_version < 0x0102) {
DPRINTF((" Version (driver %x.%x BIOS %x.%x) < 1.2, no function\n",
(apm_info.apm_driver_version>>8)&255,
apm_info.apm_driver_version&255,
(apm_info.apm_version>>8)&255,
apm_info.apm_version&255
));
goto no_cap;
}
memset(&reg, 0, sizeof(reg));
reg.eax = APM_GET_CAPABILITIES;
reg.ebx = APM_DEV_APM_BIOS;
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
DUMPREGS(&reg);
if (reg.efl&PSL_C) {
fprintf(stderr, “APM: GetCapabilities failed (code 0x%02X)\n”, reg.eax);
return 1;
}
DPRINTF((" Batteries: %d\n", reg.ebx&255));
DPRINTF((" Other caps: 0x%02X\n", reg.ecx));
#endif /
}*/
no_cap:

/* get current power status (1.1) /
DPRINTF((“APM: Get Power Status\n”));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_GET_POWER_STATUS;
reg.ebx = APM_DEV_APM_BIOS +1;
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
/

  • BH: AC status (0,1,2,0xFF)
  • BL: Batttery status (0,1,2,3,0xFF)
  • CH: Battery Flag
  • CL: Battery remaining % (0-100,0xFF)
  • DX: Battery remaining time
    */
    if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
    if (reg.efl&PSL_C) {
    fprintf(stderr, “APM: Get Power Status failed (code 0x%02X)\n”, reg.eax>>8);
    goto no_cur_pow;
    }
    DPRINTF((" AC status:"));
    switch(BH(reg)) {
    case 0x00: DPRINTF((" Offline\n")); break;
    case 0x01: DPRINTF((" Online\n")); break;
    case 0x02: DPRINTF((" On Backup Power\n")); break;
    case 0xFF:
    default: DPRINTF((" (Unknown)\n")); break;
    }
    DPRINTF((" Battery status: “));
    switch(BL(reg)) {
    case 0x00: DPRINTF((“High”)); break;
    case 0x01: DPRINTF((“Low”)); break;
    case 0x02: DPRINTF((“Critical”)); break;
    case 0x03: DPRINTF((“Charging”)); break;
    case 0xFF:
    default: DPRINTF((”(unknown)")); break;
    } DPRINTF(("\n"));
    DPRINTF((" Battery flag: 0x%02X (", CH(reg)));
    if (CH(reg) != 0xff) {
    int ch = CH(reg);
    if (ch&1) DPRINTF((“High,”));
    if (ch&2) DPRINTF((“Low,”));
    if (ch&4) DPRINTF((“Critical,”));
    if (ch&8) DPRINTF((“Charging,”));
    if (ch&16) DPRINTF((“NoSelectedBatt,”));
    if (ch&128) DPRINTF((“NoBatt”));
    }
    DPRINTF((")\n"));
    if (CL(reg)<=100) DPRINTF((" Battery remain: %d%%\n", CL(reg)));
    if ((reg.edx&0xffff)!=0xffff)
    DPRINTF((" Battery remain: %d:%02d (%s)\n",
    (reg.edx&0x7fff)/60, (reg.edx&0x7fff)%60,
    (reg.edx&0x8000)?“hour:min”:“min:sec”));
    no_cur_pow:

goto disconnect;

return 0;
disconnect:
DPRINTF((“APM: Disconnect\n”));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_DISCONNECT;
reg.ebx = APM_DEV_APM_BIOS; /APM BIOS/
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
return s;
}

Great stuff. How many machines you have tried this on? I have written
a little utlity simular to this for my own purposes but have found that
many new laptops don’t report things properly via the APM int calls as they
expect to be queried via the ACPI interfaces.

chris


kabe@sra-tohoku.co.jp wrote:

I’ve wanted to just glean the battery status of the APM laptop,
so here’s a little tool to just query the APM BIOS about it.

The next step is to turn this into a resource manager and
attatch permanently as an “APM Driver”, but this needs a
public API to provide for i.e device drivers to i.e send pulses
on Sleep event.
Is there any QSSL under-development APIs for power management to follow?

References: Advanced Power Management v1.2
URL:> http://www.microsoft.com/hwdev/archive/BUSBIOS/amp_12.asp

kabe
^L
/*

  • apm.c
  • for QNX RTP(x86)
    /
    #if QNXNTO && X86
    /
    Ok, QNXRTP on x86 platform */
    #else
    #error Only for x86 architecture.
    #endif

#include <stdio.h
#include <sys/types.h> /u_short/
#include <x86/v86.h

/NetBSD-ish defines/
/APM/
#define APM_SYSTEM_BIOS 0x15 /int 15h/
#define APM_DEV_APM_BIOS 0x0000 /for BX in int 15h BIOS call/
#define APM_INSTALLATION_CHECK 0x5300 /for AX/
#define APM_32BIT_CONNECT 0x5303
#define APM_DISCONNECT 0x5304
#define APM_GET_POWER_STATUS 0x530a
#define APM_DRIVER_VERSION 0x530e /set version of this driver/
#define APM_GET_CAPABILITIES 0x5310
struct {
u_short apm_flags;
u_short apm_version; /* BCD xx.xx /
u_short apm_driver_version; /
BCD xx.xx */
} apm_info;

/* for struct _v86reg /
#define PSL_C 1 /
carry flag mask */

#define BH(reg) ((((reg).ebx)>>8)&0xff)
#define BL(reg) (((reg).ebx)&0xff)
#define CH(reg) ((((reg).ecx)>>8)&0xff)
#define CL(reg) (((reg).ecx)&0xff)


#define DPRINTF(x) printf x
/*#define DUMPREGS(x) dumpregs(x)//
#define DUMPREGS(x) /
/

void
dumpregs(const struct _v86reg *reg)
{
DPRINTF((“AX=%04X BX=%04X CX=%04X DX=%04X\n”,
(int)reg->eax, (int)reg->ebx, (int)reg->ecx, (int)reg->edx));
DPRINTF((“FL=%04X ESI=%08lX\n”, (int)reg->efl, reg->esi));
}

int
main()
{
struct _v86reg reg;
int s;

/* installation check /
DPRINTF((“APM: Installation Check\n”));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_INSTALLATION_CHECK; /APM Installation Check/
reg.ebx = APM_DEV_APM_BIOS; /APM BIOS/
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s || reg.efl&PSL_C) { fprintf(stderr,“error on _intr_v86\n”); exit(s
); }
DUMPREGS(&reg);/XXX/
/

  • AH.AL: APM version number (BCD)
  • BH.BL: “PM”
  • CX: 0 has16bit
  • 1 has32bit
    
  • 2 Idle slows CPU
    
  • 3 disable
    
  • 4 disengage
    

*/
DPRINTF((" APM Supported: %s\n", (reg.efl&PSL_C)?“no”:“yes”));
apm_info.apm_version = reg.eax;
apm_info.apm_flags = reg.ecx;
DPRINTF((" BX signature=<%c%c>%s\n",
BH(reg), BL(reg),
((reg.ebx&0xffff)==(‘P’<<8)+‘M’)?" (ok)":" (MISMATCH)"));
DPRINTF((" BIOS APM version %x.%x\n",
(apm_info.apm_version>>8)&255, apm_info.apm_version&255 ));
if(apm_info.apm_flags & 1) { DPRINTF((" 16bit mode interface\n")); }
if(apm_info.apm_flags & 2) { DPRINTF((" 32bit mode interface\n")); }
if(apm_info.apm_flags & 4) { DPRINTF((" Slow CPU on Idle\n")); }
DPRINTF((" BIOS PM %s\n", (apm_info.apm_flags&8)?“disabled”:“enabled”))
;
DPRINTF((" BIOS PM %s\n", (apm_info.apm_flags&16)?“disengaged”:"engaged
"));

/* disconnect first, in case somebody was connected */
memset(&reg, 0, sizeof(reg));
reg.eax = APM_DISCONNECT;
reg.ebx = APM_DEV_APM_BIOS; /APM BIOS/
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
/if (reg.efl&PSL_C) { fprintf(stderr, “APM: disconnect failed\n”); exit
(1); }
/

/* connect 32 /
DPRINTF((“APM: Connect32\n”));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_32BIT_CONNECT;
reg.ebx = APM_DEV_APM_BIOS; /APM BIOS/
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
if (reg.efl&PSL_C) {
fprintf(stderr, “APM: Connect32 failed (code 0x%02X)\n”, reg.eax
);
return 1;
}
DUMPREGS(&reg);
/

  • AX: code32 realmode CS
  • (E)BX: entry point offset
  • CX: code16 realmode CS
  • ESI: 16 code32 length-1
  •  16 code16 length-1
    
  • DX: data realmode DS
  • DI: data length
    */
    DPRINTF((" 32Bit entry=%04X:%04X (len %04X+1)\n", reg.eax, reg.ebx, reg
    .esi&0xffff));
    DPRINTF((" 16Bit entry=%04X:%04X (len %04X+1)\n", reg.ecx, reg.ebx, (re
    g.esi>>16)&0xffff));
    DPRINTF((" Data region=%04X:( 0) (len %04X+1)\n", reg.edx, reg.edi));

/* set my version (needed to access 1.1- BIOS calls) /
apm_info.apm_driver_version = 0x0101; /
APM 1.1 /
DPRINTF((“APM: set Driver Version to %x.%x\n”,
(apm_info.apm_driver_version>>8)&255, apm_info.apm_driver_versi
on&255
));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_DRIVER_VERSION;
reg.ebx = APM_DEV_APM_BIOS;
reg.ecx = apm_info.apm_driver_version;
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
/

  • AH.AL: APM connection version
    */
    if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
    if (reg.efl&PSL_C) {
    fprintf(stderr, “APM: set Driver Version failed (code 0x%02X)\n”
    , reg.eax>>8);
    return 1;
    }
    DPRINTF((" Connected as version %x.%x\n", (reg.eax>>8)&255, reg.eax&255
    ));

/* get capabilities (1.2) /
#if 1 /
{/
DPRINTF((“APM: Get capabilities\n”));
if (apm_info.apm_version < 0x0102 ||
apm_info.apm_driver_version < 0x0102) {
DPRINTF((" Version (driver %x.%x BIOS %x.%x) < 1.2, no function
\n",
(apm_info.apm_driver_version>>8)&255,
apm_info.apm_driver_version&255,
(apm_info.apm_version>>8)&255,
apm_info.apm_version&255
));
goto no_cap;
}
memset(&reg, 0, sizeof(reg));
reg.eax = APM_GET_CAPABILITIES;
reg.ebx = APM_DEV_APM_BIOS;
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
DUMPREGS(&reg);
if (reg.efl&PSL_C) {
fprintf(stderr, “APM: GetCapabilities failed (code 0x%02X)\n”, r
eg.eax);
return 1;
}
DPRINTF((" Batteries: %d\n", reg.ebx&255));
DPRINTF((" Other caps: 0x%02X\n", reg.ecx));
#endif /
}*/
no_cap:

/* get current power status (1.1) /
DPRINTF((“APM: Get Power Status\n”));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_GET_POWER_STATUS;
reg.ebx = APM_DEV_APM_BIOS +1;
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
/

  • BH: AC status (0,1,2,0xFF)
  • BL: Batttery status (0,1,2,3,0xFF)
  • CH: Battery Flag
  • CL: Battery remaining % (0-100,0xFF)
  • DX: Battery remaining time
    */
    if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
    if (reg.efl&PSL_C) {
    fprintf(stderr, “APM: Get Power Status failed (code 0x%02X)\n”,
    reg.eax>>8);
    goto no_cur_pow;
    }
    DPRINTF((" AC status:"));
    switch(BH(reg)) {
    case 0x00: DPRINTF((" Offline\n")); break;
    case 0x01: DPRINTF((" Online\n")); break;
    case 0x02: DPRINTF((" On Backup Power\n")); break;
    case 0xFF:
    default: DPRINTF((" (Unknown)\n")); break;
    }
    DPRINTF((" Battery status: “));
    switch(BL(reg)) {
    case 0x00: DPRINTF((“High”)); break;
    case 0x01: DPRINTF((“Low”)); break;
    case 0x02: DPRINTF((“Critical”)); break;
    case 0x03: DPRINTF((“Charging”)); break;
    case 0xFF:
    default: DPRINTF((”(unknown)")); break;
    } DPRINTF(("\n"));
    DPRINTF((" Battery flag: 0x%02X (", CH(reg)));
    if (CH(reg) != 0xff) {
    int ch = CH(reg);
    if (ch&1) DPRINTF((“High,”));
    if (ch&2) DPRINTF((“Low,”));
    if (ch&4) DPRINTF((“Critical,”));
    if (ch&8) DPRINTF((“Charging,”));
    if (ch&16) DPRINTF((“NoSelectedBatt,”));
    if (ch&128) DPRINTF((“NoBatt”));
    }
    DPRINTF((")\n"));
    if (CL(reg)<=100) DPRINTF((" Battery remain: %d%%\n", CL(reg)));
    if ((reg.edx&0xffff)!=0xffff)
    DPRINTF((" Battery remain: %d:%02d (%s)\n",
    (reg.edx&0x7fff)/60, (reg.edx&0x7fff)%60,
    (reg.edx&0x8000)?“hour:min”:“min:sec”));
    no_cur_pow:

goto disconnect;

return 0;
disconnect:
DPRINTF((“APM: Disconnect\n”));
memset(&reg, 0, sizeof(reg));
reg.eax = APM_DISCONNECT;
reg.ebx = APM_DEV_APM_BIOS; /APM BIOS/
s = _intr_v86(APM_SYSTEM_BIOS, &reg, NULL,0);
if (s) { fprintf(stderr,“error on _intr_v86\n”); exit(s); }
return s;
}
\


Chris McKillop <cdm@qnx.com> “The faster I go, the behinder I get.”
Software Engineer, QSSL – Lewis Carroll –
http://qnx.wox.org/

a little utlity simular to this for my own purposes but have found that
many new laptops don’t report things properly via the APM int calls as they
expect to be queried via the ACPI interfaces.

I have only pre-ACPI machines for now to test out…


Looking at the Phoenix site, APM support seems to be eventually
dropped, so I have to re-investigate ACPI.
Glanced at the spec now, can’t get the picture in first place.

Regarding embedded boards, not PC laptops; is ACPI the selection or APM lives?

kabe

a little utlity simular to this for my own purposes but have found that
many new laptops don’t report things properly via the APM int calls as they
expect to be queried via the ACPI interfaces.

I have only pre-ACPI machines for now to test out…

Yeah. My laptop supports both. People that have used my little utility
have had a bout a 50% success rating thus far.


Looking at the Phoenix site, APM support seems to be eventually
dropped, so I have to re-investigate ACPI.
Glanced at the spec now, can’t get the picture in first place.

Regarding embedded boards, not PC laptops; is ACPI the selection or APM lives?

I think that ACPI is the way everything is going - esp. with respect to
PCI and multiple power down levels.

chris


Chris McKillop <cdm@qnx.com> “The faster I go, the behinder I get.”
Software Engineer, QSSL – Lewis Carroll –
http://qnx.wox.org/