Skip to content

Commit 435a66b

Browse files
committed
Add a pretty bare bones implementation of APM 1.1 that can turn on and off the power supply and grahics card. This implementation would also query battery life but I don't have anything to test it on.
1 parent 1dcc99d commit 435a66b

3 files changed

Lines changed: 302 additions & 7 deletions

File tree

8086tiny.c

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
#ifndef GRAPHICS_UPDATE_DELAY
6666
#define GRAPHICS_UPDATE_DELAY 360000
6767
#endif
68+
#define APM_INST_DELAY 1000
6869
#define KEYBOARD_TIMER_UPDATE_DELAY 20000
6970
#define HALT_TIME_MICROSECONDS 100
7071
#define KEYBOARD_TIMER_DELAY_FROM_HALT 1000
@@ -205,11 +206,14 @@ unsigned char mem[RAM_SIZE + 16], io_ports[IO_PORT_COUNT + 16],
205206
hlt_this_time, setting_ss, prior_setting_ss, reset_ip_after_rep_trace,
206207
shift_count;
207208
unsigned short *regs16, reg_ip, seg_override, file_index, wave_counter, reg_ip_before_rep_trace;
208-
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;
209-
int op_result, disk[3], scratch_int;
209+
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;
210+
int op_result, disk[3], scratch_int, device_status;
210211
time_t clock_buf;
211212
struct timeb ms_clock;
212213

214+
#define DEVICE_STATUS_GRAPHICS 1
215+
#define DEVICE_STATUS_FASTCPU 2
216+
213217
#ifndef NO_GRAPHICS
214218
SDL_AudioSpec sdl_audio = {44100, AUDIO_U8, 1, 0, 128};
215219
SDL_Surface *sdl_screen;
@@ -220,6 +224,7 @@ unsigned short vid_addr_lookup[VIDEO_RAM_SIZE], cga_colors[4] = {0 /* Black */,
220224
// Helper functions
221225

222226
void callxms();
227+
void callapm();
223228

224229
// Set carry flag
225230
char set_CF(int new_CF)
@@ -848,6 +853,8 @@ int main(int argc, char **argv)
848853
: (errno == EINVAL) ? 0x04 /* out of range on disk device */ : 0xAA /* no disk */;
849854
OPCODE 4: // XMS
850855
callxms();
856+
OPCODE 5:
857+
callapm();
851858
}
852859
if ((uint8_t)i_data0 == 11 // ud2
853860
|| (uint8_t)i_data0 >= 32)
@@ -1023,7 +1030,7 @@ int main(int argc, char **argv)
10231030
graphics_inst_counter = 0;
10241031
hlt_this_time = 0;
10251032
// Video card in graphics mode?
1026-
if (io_ports[0x3B8] & 2)
1033+
if (io_ports[0x3B8] & 2 && device_status & DEVICE_STATUS_GRAPHICS)
10271034
{
10281035
// If we don't already have an SDL window open, set it up and compute color and video memory translation tables
10291036
if (!sdl_screen)
@@ -1057,6 +1064,11 @@ int main(int argc, char **argv)
10571064
SDL_PumpEvents();
10581065
}
10591066
#endif
1067+
if (!setting_ss && !hlt_this_time && !(device_status & DEVICE_STATUS_FASTCPU) && ++apm_inst_counter >= APM_INST_DELAY) {
1068+
hlt_this_time = 1;
1069+
apm_inst_counter = 0;
1070+
}
1071+
10601072
if (hlt_this_time) {
10611073
struct timespec ts;
10621074
ts.tv_sec = 0;
@@ -1240,6 +1252,54 @@ uint32_t getfreexms() {
12401252
return lower;
12411253
}
12421254

1255+
void callapm() {
1256+
char buf[12];
1257+
int h, tmp;
1258+
switch (regs8[REG_AH]) {
1259+
OPCODE_CHAIN 0: // CPU slow
1260+
device_status &= ~DEVICE_STATUS_FASTCPU;
1261+
OPCODE 1: // CPU normal
1262+
device_status |= ~DEVICE_STATUS_FASTCPU;
1263+
OPCODE 2: // graphics card off
1264+
device_status &= ~DEVICE_STATUS_GRAPHICS;
1265+
OPCODE 3: // graphics card on
1266+
device_status |= DEVICE_STATUS_GRAPHICS;
1267+
OPCODE 4: // Get battery status
1268+
#ifdef _WIN32
1269+
regs8[REG_BL] = 0xff;
1270+
#else
1271+
h = open("/sys/class/power_supply/BAT0/status", O_RDONLY);
1272+
if (h >= 0 && (0 < read(h, buf, 12)))
1273+
regs8[REG_BL] = buf[0] == 'C' ? 3 /* charging */ : 2 /* critical */;
1274+
else
1275+
regs8[REG_BL] = 0xFF; // Unable
1276+
if (h >= 0) close(h);
1277+
h = open("/sys/class/power_supply/BAT0/capacity", O_RDONLY);
1278+
if (h >= 0 && ((tmp = read(h, buf, 10)) > 0))
1279+
buf[tmp] = 0, regs8[REG_CL] = atoi(buf);
1280+
else
1281+
regs8[REG_CL] = 0xFF; // Unable
1282+
if (h >= 0) close(h);
1283+
if (regs8[REG_CL] != 0xFF && regs8[REG_BL] == 2) {
1284+
if (regs8[REG_CL] >= 50) regs8[REG_BL] = 0; // High
1285+
else if (regs8[REG_CL] >= 10) regs8[REG_BL] = 1; // Low
1286+
// If we can't get battery charge and we're not charging it's critical
1287+
}
1288+
#endif
1289+
OPCODE 5: // Get AC status
1290+
#ifdef _WIN32
1291+
regs8[REG_BH] = 0xff;
1292+
#else
1293+
h = open("/sys/class/power_supply/AC/online", O_RDONLY);
1294+
if (h >= 0 && (0 < read(h, buf, 2)))
1295+
regs8[REG_BH] = buf[0] - 1; // 0 = offline, 1 = online
1296+
else
1297+
regs8[REG_BH] = 0xFF; // Unable
1298+
if (h >= 0) close(h);
1299+
#endif
1300+
}
1301+
}
1302+
12431303

12441304
void callxms() {
12451305
uint32_t ii, free;

bios.asm

Lines changed: 226 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,15 @@ bios_entry:
8888

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

91+
cmp byte [cs:boot_state], 0
92+
jne .warm_early_boot
9193
mov [cs:boot_device], dl
94+
.warm_early_boot:
95+
mov [cs:apm_flags], byte 0
96+
97+
; Set CPU to high power
98+
mov ah, 1
99+
db 0x0f, 0x05
92100

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

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

122+
; Graphics power on
123+
mov ah, 3
124+
db 0x0f, 0x05
125+
114126
pop dx
115127

116128
pop ax
@@ -2610,6 +2622,8 @@ int15: ; Here we do not support any of the functions, and just return
26102622
; je int15_intercept
26112623
cmp ah, 0x88
26122624
je int15_getextmem
2625+
cmp ah, 0x53
2626+
je int15_apm
26132627

26142628
; Otherwise, function not supported
26152629

@@ -2643,10 +2657,218 @@ int15_getextmem:
26432657
db 0Fh, 04h ; call into XMS interface
26442658
cmc ; if it returned NC, it means error
26452659
jnc .ret ; no error -->
2646-
mov ah, 86h
2660+
; No XMS interface but high memory exists
2661+
mov ax, 64
2662+
clc
26472663
.ret:
26482664
jmp reach_stack_carry
26492665

2666+
int15_apmversion:
2667+
cmp cx, 0x101
2668+
ja .nope
2669+
mov ax, 0x101
2670+
jmp reach_stack_clc
2671+
.nope mov ah, 0x0b
2672+
jmp reach_stack_stc
2673+
2674+
int15_apmbogonstate:
2675+
jmp reach_stack_clc
2676+
2677+
int15_apm:
2678+
cmp al, 0
2679+
je .apmchk
2680+
cmp al, 1
2681+
je .real
2682+
cmp al, 2
2683+
je .prot16
2684+
cmp al, 3
2685+
je .prot32
2686+
cmp al, 4
2687+
je .realdc
2688+
cmp al, 5
2689+
je .cpuidle
2690+
cmp al, 6
2691+
je .cpubusy
2692+
cmp al, 7
2693+
je .setpower
2694+
cmp al, 8
2695+
je .ends
2696+
cmp al, 9
2697+
je .ends
2698+
cmp al, 10
2699+
je .getbattery
2700+
cmp al, 11
2701+
je .getevent
2702+
cmp al, 12
2703+
je .getpower
2704+
cmp al, 13
2705+
je int15_apmbogonstate
2706+
cmp al, 14
2707+
je int15_apmversion
2708+
mov ah, 0ah
2709+
jmp .fail
2710+
2711+
.prot16: ; 16 bit protected mode, but we're an 8086
2712+
mov ah, 6
2713+
jmp .fail
2714+
.prot32: ; 32 bit protected mode, lolno
2715+
mov ah, 8
2716+
.fail jmp reach_stack_stc
2717+
.apmchk:
2718+
mov ax, 0x101
2719+
mov bx, 'PM'
2720+
mov cx, 4
2721+
.ok jmp reach_stack_clc
2722+
2723+
.realdc: ; Real mode APM disconnect
2724+
test [cs:apm_flags], byte 1
2725+
jz .notconnected
2726+
mov [cs:apm_flags], byte 0
2727+
mov ah, 1 ; Take CPU out of idle
2728+
db 0x0f, 0x05
2729+
jmp reach_stack_clc
2730+
.notconnected:
2731+
mov ah, 0x03
2732+
jmp .fail
2733+
2734+
.real: ; Real mode connect -- neither of these do anything interesting
2735+
; Suspend would turn off the mouse; so we can't easily implement a good idle policy
2736+
test [cs:apm_flags], byte 1
2737+
jnz .realalready
2738+
or bx, bx
2739+
jnz .fail
2740+
mov [cs:apm_flags], byte 1
2741+
jmp .ok
2742+
.realalready:
2743+
mov ah, 0x02
2744+
jmp .fail
2745+
2746+
.cpuidle:
2747+
test [cs:apm_flags], byte 1
2748+
jz .notconnected
2749+
push ax
2750+
mov ah, 0
2751+
db 0x0f, 0x05
2752+
pop ax
2753+
jmp .ok
2754+
2755+
.cpubusy: ; Documentation says bios should be able to tell; this bios can't
2756+
; However this bios can only slow on cpuidle so it's all right
2757+
test [cs:apm_flags], byte 1
2758+
jz .notconnected
2759+
push ax
2760+
mov ah, 1
2761+
db 0x0f, 0x05
2762+
pop ax
2763+
jmp .ok
2764+
2765+
.ends: ; Enable/disable is NOP
2766+
test [cs:apm_flags], byte 1
2767+
jz .notconnected
2768+
cmp bx, 0xffff
2769+
je .ok
2770+
jmp .fail
2771+
2772+
.setpower:
2773+
test [cs:apm_flags], byte 1
2774+
jz .notconnected
2775+
cmp cx, 3
2776+
ja .nos
2777+
je .cxk
2778+
cmp cx, 0
2779+
jne .nxs
2780+
.cxk cmp bx, 1
2781+
je .system
2782+
cmp bx, 0x100
2783+
je .graphics
2784+
.nodevice:
2785+
mov ah, 9
2786+
jmp .fail
2787+
.nos mov ah, 0x0a
2788+
jmp .fail
2789+
.nxs mov ah, 0x60
2790+
jmp .fail
2791+
2792+
.system cmp cx, 3
2793+
je .poweroff
2794+
jmp .ok
2795+
.graphics:
2796+
push ax
2797+
mov ah, 3
2798+
cmp cx, 3
2799+
and [cs:apm_flags], byte 0xfb
2800+
je .graphicsoff
2801+
mov ah, 2
2802+
or [cs:apm_flags], byte 4
2803+
.graphicsoff:
2804+
db 0x0f, 0x05
2805+
pop ax
2806+
jmp .ok
2807+
2808+
.poweroff: ; Turn off the system
2809+
mov bx, bp ; Get error code from caller, if any
2810+
mov ax, dx
2811+
jmp 0:0
2812+
2813+
.getpower:
2814+
cmp bx, 0x100
2815+
je .getgraphics
2816+
cmp bx, 1 ; System
2817+
jne .nodevice
2818+
mov cx, 1
2819+
jmp .ok
2820+
.getgraphics:
2821+
xor cx, cx
2822+
test [cs:apm_flags], byte 4
2823+
jz .getgraphicson
2824+
mov cl, 3
2825+
.getgraphicson:
2826+
jmp .ok
2827+
2828+
.getbattery:
2829+
cmp bx, 1
2830+
ja .nodevice ; asks for specific battery; but we are APM 1.1
2831+
push ax
2832+
; This call always succeeds
2833+
mov ah, 4
2834+
db 0x0f, 0x05
2835+
mov ah, 5
2836+
db 0x0f, 0x05
2837+
pop ax
2838+
jmp reach_stack_clc
2839+
2840+
.getevent:
2841+
; There's only one event: battery
2842+
test [cs:apm_flags], byte 1
2843+
jz .notconnected
2844+
push bp
2845+
mov bp, sp
2846+
push ax
2847+
push bx
2848+
push cx
2849+
mov ah, 4
2850+
db 0x0f, 0x05
2851+
cmp bl, 2
2852+
jne .gennocrit
2853+
test [cs:apm_flags], byte 2
2854+
jz .gencritalready
2855+
or [cs:apm_flags], byte 2
2856+
; I hate this specification. What is labelled as low battery notification is critical battery notification and
2857+
; what is labelled as critical battery notification is critical batter after waking from unscheduled suspend. -JH
2858+
mov [bp - 4], word 5 ; AX = battery notification
2859+
clc
2860+
jmp .geexit
2861+
.gennocrit:
2862+
and [cs:apm_flags], byte 0xfd
2863+
.gencritalready:
2864+
mov [bp - 1], byte 80h ; AH = no event
2865+
stc
2866+
.geexit:
2867+
pop cx
2868+
pop bx
2869+
pop ax
2870+
pop bp
2871+
jmp reach_stack_carry
26502872

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

@@ -4050,6 +4272,9 @@ crt_curpos_y_last db 0
40504272
last_int8_msec dw 0
40514273
last_key_sdl db 0
40524274

4275+
; APM data
4276+
apm_flags db 0
4277+
40534278
; Now follow the tables for instruction decode helping
40544279

40554280
align 8

0 commit comments

Comments
 (0)