Skip to content

Commit

Permalink
Add a pretty bare bones implementation of APM 1.1 that can turn on an…
Browse files Browse the repository at this point in the history
…d off the power supply and grahics card. This implementation would also query battery life but I don't have anything to test it on.
  • Loading branch information
joshudson committed Mar 5, 2024
1 parent 1dcc99d commit 2362433
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 7 deletions.
66 changes: 63 additions & 3 deletions 8086tiny.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
#ifndef GRAPHICS_UPDATE_DELAY
#define GRAPHICS_UPDATE_DELAY 360000
#endif
#define APM_INST_DELAY 1000
#define KEYBOARD_TIMER_UPDATE_DELAY 20000
#define HALT_TIME_MICROSECONDS 100
#define KEYBOARD_TIMER_DELAY_FROM_HALT 1000
Expand Down Expand Up @@ -205,11 +206,14 @@ unsigned char mem[RAM_SIZE + 16], io_ports[IO_PORT_COUNT + 16],
hlt_this_time, setting_ss, prior_setting_ss, reset_ip_after_rep_trace,
shift_count;
unsigned short *regs16, reg_ip, seg_override, file_index, wave_counter, reg_ip_before_rep_trace;
unsigned int op_source, op_dest, rm_addr, op_to_addr, op_from_addr, i_data0, i_data1, i_data2, scratch_uint, scratch2_uint, keyboard_timer_inst_counter, graphics_inst_counter, set_flags_type, GRAPHICS_X, GRAPHICS_Y, pixel_colors[16], vmem_ctr;
int op_result, disk[3], scratch_int;
unsigned int op_source, op_dest, rm_addr, op_to_addr, op_from_addr, i_data0, i_data1, i_data2, scratch_uint, scratch2_uint, keyboard_timer_inst_counter, graphics_inst_counter, apm_inst_counter, set_flags_type, GRAPHICS_X, GRAPHICS_Y, pixel_colors[16], vmem_ctr;
int op_result, disk[3], scratch_int, device_status;
time_t clock_buf;
struct timeb ms_clock;

#define DEVICE_STATUS_GRAPHICS 1
#define DEVICE_STATUS_FASTCPU 2

#ifndef NO_GRAPHICS
SDL_AudioSpec sdl_audio = {44100, AUDIO_U8, 1, 0, 128};
SDL_Surface *sdl_screen;
Expand All @@ -220,6 +224,7 @@ unsigned short vid_addr_lookup[VIDEO_RAM_SIZE], cga_colors[4] = {0 /* Black */,
// Helper functions

void callxms();
void callapm();

// Set carry flag
char set_CF(int new_CF)
Expand Down Expand Up @@ -848,6 +853,8 @@ int main(int argc, char **argv)
: (errno == EINVAL) ? 0x04 /* out of range on disk device */ : 0xAA /* no disk */;
OPCODE 4: // XMS
callxms();
OPCODE 5:
callapm();
}
if ((uint8_t)i_data0 == 11 // ud2
|| (uint8_t)i_data0 >= 32)
Expand Down Expand Up @@ -1023,7 +1030,7 @@ int main(int argc, char **argv)
graphics_inst_counter = 0;
hlt_this_time = 0;
// Video card in graphics mode?
if (io_ports[0x3B8] & 2)
if (io_ports[0x3B8] & 2 && device_status & DEVICE_STATUS_GRAPHICS)
{
// If we don't already have an SDL window open, set it up and compute color and video memory translation tables
if (!sdl_screen)
Expand Down Expand Up @@ -1057,6 +1064,11 @@ int main(int argc, char **argv)
SDL_PumpEvents();
}
#endif
if (!setting_ss && !hlt_this_time && !(device_status & DEVICE_STATUS_FASTCPU) && ++apm_inst_counter >= APM_INST_DELAY) {
hlt_this_time = 1;
apm_inst_counter = 0;
}

if (hlt_this_time) {
struct timespec ts;
ts.tv_sec = 0;
Expand Down Expand Up @@ -1240,6 +1252,54 @@ uint32_t getfreexms() {
return lower;
}

void callapm() {
char buf[12];
int h, tmp;
switch (regs8[REG_AH]) {
OPCODE_CHAIN 0: // CPU slow
device_status &= ~DEVICE_STATUS_FASTCPU;
OPCODE 1: // CPU normal
device_status |= DEVICE_STATUS_FASTCPU;
OPCODE 2: // graphics card off
device_status &= ~DEVICE_STATUS_GRAPHICS;
OPCODE 3: // graphics card on
device_status |= DEVICE_STATUS_GRAPHICS;
OPCODE 4: // Get battery status
#ifdef _WIN32
regs8[REG_BL] = 0xff;
#else
h = open("/sys/class/power_supply/BAT0/status", O_RDONLY);
if (h >= 0 && (0 < read(h, buf, 12)))
regs8[REG_BL] = buf[0] == 'C' ? 3 /* charging */ : 2 /* critical */;
else
regs8[REG_BL] = 0xFF; // Unable
if (h >= 0) close(h);
h = open("/sys/class/power_supply/BAT0/capacity", O_RDONLY);
if (h >= 0 && ((tmp = read(h, buf, 10)) > 0))
buf[tmp] = 0, regs8[REG_CL] = atoi(buf);
else
regs8[REG_CL] = 0xFF; // Unable
if (h >= 0) close(h);
if (regs8[REG_CL] != 0xFF && regs8[REG_BL] == 2) {
if (regs8[REG_CL] >= 50) regs8[REG_BL] = 0; // High
else if (regs8[REG_CL] >= 10) regs8[REG_BL] = 1; // Low
// If we can't get battery charge and we're not charging it's critical
}
#endif
OPCODE 5: // Get AC status
#ifdef _WIN32
regs8[REG_BH] = 0xff;
#else
h = open("/sys/class/power_supply/AC/online", O_RDONLY);
if (h >= 0 && (0 < read(h, buf, 2)))
regs8[REG_BH] = buf[0] - 1; // 0 = offline, 1 = online
else
regs8[REG_BH] = 0xFF; // Unable
if (h >= 0) close(h);
#endif
}
}


void callxms() {
uint32_t ii, free;
Expand Down
227 changes: 226 additions & 1 deletion bios.asm
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,15 @@ bios_entry:

; Now we can do whatever we want! DL starts off being the boot disk.

cmp byte [cs:boot_state], 0
jne .warm_early_boot
mov [cs:boot_device], dl
.warm_early_boot:
mov [cs:apm_flags], byte 0

; Set CPU to high power
mov ah, 1
db 0x0f, 0x05

; Set up Hercules graphics support. We start with the adapter in text mode

Expand All @@ -111,6 +119,10 @@ bios_entry:
mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y)
out dx, al

; Graphics power on
mov ah, 3
db 0x0f, 0x05

pop dx

pop ax
Expand Down Expand Up @@ -2610,6 +2622,8 @@ int15: ; Here we do not support any of the functions, and just return
; je int15_intercept
cmp ah, 0x88
je int15_getextmem
cmp ah, 0x53
je int15_apm

; Otherwise, function not supported

Expand Down Expand Up @@ -2643,10 +2657,218 @@ int15_getextmem:
db 0Fh, 04h ; call into XMS interface
cmc ; if it returned NC, it means error
jnc .ret ; no error -->
mov ah, 86h
; No XMS interface but high memory exists
mov ax, 64
clc
.ret:
jmp reach_stack_carry

int15_apmversion:
cmp cx, 0x101
ja .nope
mov ax, 0x101
jmp reach_stack_clc
.nope mov ah, 0x0b
jmp reach_stack_stc

int15_apmbogonstate:
jmp reach_stack_clc

int15_apm:
cmp al, 0
je .apmchk
cmp al, 1
je .real
cmp al, 2
je .prot16
cmp al, 3
je .prot32
cmp al, 4
je .realdc
cmp al, 5
je .cpuidle
cmp al, 6
je .cpubusy
cmp al, 7
je .setpower
cmp al, 8
je .ends
cmp al, 9
je .ends
cmp al, 10
je .getbattery
cmp al, 11
je .getevent
cmp al, 12
je .getpower
cmp al, 13
je int15_apmbogonstate
cmp al, 14
je int15_apmversion
mov ah, 0ah
jmp .fail

.prot16: ; 16 bit protected mode, but we're an 8086
mov ah, 6
jmp .fail
.prot32: ; 32 bit protected mode, lolno
mov ah, 8
.fail jmp reach_stack_stc
.apmchk:
mov ax, 0x101
mov bx, 'PM'
mov cx, 4
.ok jmp reach_stack_clc

.realdc: ; Real mode APM disconnect
test [cs:apm_flags], byte 1
jz .notconnected
mov [cs:apm_flags], byte 0
mov ah, 1 ; Take CPU out of idle
db 0x0f, 0x05
jmp reach_stack_clc
.notconnected:
mov ah, 0x03
jmp .fail

.real: ; Real mode connect -- neither of these do anything interesting
; Suspend would turn off the mouse; so we can't easily implement a good idle policy
test [cs:apm_flags], byte 1
jnz .realalready
or bx, bx
jnz .fail
mov [cs:apm_flags], byte 1
jmp .ok
.realalready:
mov ah, 0x02
jmp .fail

.cpuidle:
test [cs:apm_flags], byte 1
jz .notconnected
push ax
mov ah, 0
db 0x0f, 0x05
pop ax
jmp .ok

.cpubusy: ; Documentation says bios should be able to tell; this bios can't
; However this bios can only slow on cpuidle so it's all right
test [cs:apm_flags], byte 1
jz .notconnected
push ax
mov ah, 1
db 0x0f, 0x05
pop ax
jmp .ok

.ends: ; Enable/disable is NOP
test [cs:apm_flags], byte 1
jz .notconnected
cmp bx, 0xffff
je .ok
jmp .fail

.setpower:
test [cs:apm_flags], byte 1
jz .notconnected
cmp cx, 3
ja .nos
je .cxk
cmp cx, 0
jne .nxs
.cxk cmp bx, 1
je .system
cmp bx, 0x100
je .graphics
.nodevice:
mov ah, 9
jmp .fail
.nos mov ah, 0x0a
jmp .fail
.nxs mov ah, 0x60
jmp .fail

.system cmp cx, 3
je .poweroff
jmp .ok
.graphics:
push ax
mov ah, 3
cmp cx, 3
and [cs:apm_flags], byte 0xfb
je .graphicsoff
mov ah, 2
or [cs:apm_flags], byte 4
.graphicsoff:
db 0x0f, 0x05
pop ax
jmp .ok

.poweroff: ; Turn off the system
mov bx, bp ; Get error code from caller, if any
mov ax, dx
jmp 0:0

.getpower:
cmp bx, 0x100
je .getgraphics
cmp bx, 1 ; System
jne .nodevice
mov cx, 1
jmp .ok
.getgraphics:
xor cx, cx
test [cs:apm_flags], byte 4
jz .getgraphicson
mov cl, 3
.getgraphicson:
jmp .ok

.getbattery:
cmp bx, 1
ja .nodevice ; asks for specific battery; but we are APM 1.1
push ax
; This call always succeeds
mov ah, 4
db 0x0f, 0x05
mov ah, 5
db 0x0f, 0x05
pop ax
jmp reach_stack_clc

.getevent:
; There's only one event: battery
test [cs:apm_flags], byte 1
jz .notconnected
push bp
mov bp, sp
push ax
push bx
push cx
mov ah, 4
db 0x0f, 0x05
cmp bl, 2
jne .gennocrit
test [cs:apm_flags], byte 2
jz .gencritalready
or [cs:apm_flags], byte 2
; I hate this specification. What is labelled as low battery notification is critical battery notification and
; what is labelled as critical battery notification is critical batter after waking from unscheduled suspend. -JH
mov [bp - 4], word 5 ; AX = battery notification
clc
jmp .geexit
.gennocrit:
and [cs:apm_flags], byte 0xfd
.gencritalready:
mov [bp - 1], byte 80h ; AH = no event
stc
.geexit:
pop cx
pop bx
pop ax
pop bp
jmp reach_stack_carry

; ************************* INT 16h handler - keyboard

Expand Down Expand Up @@ -4050,6 +4272,9 @@ crt_curpos_y_last db 0
last_int8_msec dw 0
last_key_sdl db 0

; APM data
apm_flags db 0

; Now follow the tables for instruction decode helping

align 8
Expand Down
Loading

0 comments on commit 2362433

Please sign in to comment.