2 * Copyright 2006-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
7 #include <KernelExport.h>
10 #include <descriptors.h>
11 #include <generic_syscall.h>
13 #include <boot/kernel_args.h>
18 # define TRACE(x) dprintf x
23 #define APM_ERROR_DISABLED 0x01
24 #define APM_ERROR_DISCONNECTED 0x03
25 #define APM_ERROR_UNKNOWN_DEVICE 0x09
26 #define APM_ERROR_OUT_OF_RANGE 0x0a
27 #define APM_ERROR_DISENGAGED 0x0b
28 #define APM_ERROR_NOT_SUPPORTED 0x0c
29 #define APM_ERROR_RESUME_TIMER_DISABLED 0x0d
30 #define APM_ERROR_UNABLE_TO_ENTER_STATE 0x60
31 #define APM_ERROR_NO_EVENTS_PENDING 0x80
32 #define APM_ERROR_APM_NOT_PRESENT 0x86
34 #define CARRY_FLAG 0x01
36 extern void *gDmaAddress
;
37 extern addr_t gBiosBase
;
39 static bool sAPMEnabled
= false;
47 bios_regs() : eax(0), ebx(0), ecx(0), edx(0), esi(0), flags(0) {}
59 apm_error(uint32 error
)
62 case APM_ERROR_DISABLED
:
63 return "Power Management disabled";
64 case APM_ERROR_DISCONNECTED
:
65 return "Interface disconnected";
66 case APM_ERROR_UNKNOWN_DEVICE
:
67 return "Unrecognized device ID";
68 case APM_ERROR_OUT_OF_RANGE
:
69 return "Parameter value out of range";
70 case APM_ERROR_DISENGAGED
:
71 return "Interface not engaged";
72 case APM_ERROR_NOT_SUPPORTED
:
73 return "Function not supported";
74 case APM_ERROR_RESUME_TIMER_DISABLED
:
75 return "Resume timer disabled";
76 case APM_ERROR_UNABLE_TO_ENTER_STATE
:
77 return "Unable to enter requested state";
78 case APM_ERROR_NO_EVENTS_PENDING
:
79 return "No power management events pending";
80 case APM_ERROR_APM_NOT_PRESENT
:
81 return "APM not present";
84 return "Unknown error";
91 call_apm_bios(bios_regs
*regs
)
94 // TODO: Fix this for GCC 4.3! The direct reference to sAPMBiosEntry
95 // in the asm below causes undefined references.
99 "lcall *%%cs:sAPMBiosEntry; "
105 : "=a" (regs
->eax
), "=b" (regs
->ebx
), "=c" (regs
->ecx
), "=d" (regs
->edx
),
106 "=S" (regs
->esi
), "=m" (regs
->flags
)
107 : "a" (regs
->eax
), "b" (regs
->ebx
), "c" (regs
->ecx
)
108 : "memory", "edi", "cc");
110 if (regs
->flags
& CARRY_FLAG
)
121 apm_get_event(uint16
&event
, uint16
&info
)
124 regs
.eax
= BIOS_APM_GET_EVENT
;
126 if (call_apm_bios(®s
) != B_OK
)
129 event
= regs
.ebx
& 0xffff;
130 info
= regs
.ecx
& 0xffff;
136 apm_set_state(uint16 device
, uint16 state
)
139 regs
.eax
= BIOS_APM_SET_STATE
;
143 status_t status
= call_apm_bios(®s
);
147 TRACE(("apm_set_state() error: %s\n", apm_error(regs
.eax
)));
153 apm_enable_power_management(uint16 device
, bool enable
)
156 regs
.eax
= BIOS_APM_ENABLE
;
158 regs
.ecx
= enable
? 0x01 : 0x00;
160 return call_apm_bios(®s
);
165 apm_engage_power_management(uint16 device
, bool engage
)
168 regs
.eax
= BIOS_APM_ENGAGE
;
170 regs
.ecx
= engage
? 0x01 : 0x00;
172 return call_apm_bios(®s
);
177 apm_driver_version(uint16 version
)
179 dprintf("version: %x\n", version
);
181 regs
.eax
= BIOS_APM_VERSION
;
184 if (call_apm_bios(®s
) != B_OK
)
187 dprintf("eax: %lx, flags: %lx\n", regs
.eax
, regs
.flags
);
194 apm_daemon(void *arg
, int iteration
)
198 if (apm_get_event(event
, info
) != B_OK
)
201 dprintf("APM event: %x, info: %x\n", event
, info
);
206 get_apm_battery_info(apm_battery_info
*info
)
209 regs
.eax
= BIOS_APM_GET_POWER_STATUS
;
210 regs
.ebx
= APM_ALL_DEVICES
;
213 status_t status
= call_apm_bios(®s
);
217 uint16 lineStatus
= (regs
.ebx
>> 8) & 0xff;
218 if (lineStatus
== 0xff)
219 return B_NOT_SUPPORTED
;
221 info
->online
= lineStatus
!= 0 && lineStatus
!= 2;
222 info
->percent
= regs
.ecx
& 0xff;
223 if (info
->percent
> 100 || info
->percent
< 0)
226 info
->time_left
= info
->percent
>= 0 ? (int32
)(regs
.edx
& 0xffff) : -1;
227 if (info
->time_left
& 0x8000)
228 info
->time_left
= (info
->time_left
& 0x7fff) * 60;
235 apm_control(const char *subsystem
, uint32 function
,
236 void *buffer
, size_t bufferSize
)
238 struct apm_battery_info info
;
239 if (bufferSize
!= sizeof(struct apm_battery_info
))
243 case APM_GET_BATTERY_INFO
:
244 status_t status
= get_apm_battery_info(&info
);
248 return user_memcpy(buffer
, &info
, sizeof(struct apm_battery_info
));
262 return B_NOT_SUPPORTED
;
264 cpu_status state
= disable_interrupts();
266 status_t status
= apm_set_state(APM_ALL_DEVICES
, APM_POWER_STATE_OFF
);
268 restore_interrupts(state
);
274 apm_init(kernel_args
*args
)
276 const apm_info
&info
= args
->platform_args
.apm
;
278 TRACE(("apm_init()\n"));
280 if ((info
.version
& 0xf) < 2) {
281 // no APM or connect failed
285 TRACE((" code32: 0x%x, 0x%lx, length 0x%x\n",
286 info
.code32_segment_base
, info
.code32_segment_offset
, info
.code32_segment_length
));
287 TRACE((" code16: 0x%x, length 0x%x\n",
288 info
.code16_segment_base
, info
.code16_segment_length
));
289 TRACE((" data: 0x%x, length 0x%x\n",
290 info
.data_segment_base
, info
.data_segment_length
));
292 // get APM setting - safemode settings override kernel settings
296 void *handle
= load_driver_settings("kernel");
297 if (handle
!= NULL
) {
298 apm
= get_driver_boolean_parameter(handle
, "apm", false, false);
299 unload_driver_settings(handle
);
302 handle
= load_driver_settings(B_SAFEMODE_DRIVER_SETTINGS
);
303 if (handle
!= NULL
) {
304 apm
= !get_driver_boolean_parameter(handle
, B_SAFEMODE_DISABLE_APM
, !apm
, !apm
);
305 unload_driver_settings(handle
);
311 // Apparently, some broken BIOS try to access segment 0x40 for the BIOS
312 // data section - we make sure it can by setting up the GDT accordingly
313 // (the first 640kB are mapped as DMA area in arch_vm_init()).
314 addr_t biosData
= (addr_t
)gDmaAddress
+ 0x400;
316 for (uint32 i
= 0; i
< args
->num_cpus
; i
++) {
317 segment_descriptor
* gdt
= get_gdt(i
);
319 set_segment_descriptor(&gdt
[BIOS_DATA_SEGMENT
], biosData
,
320 B_PAGE_SIZE
- biosData
, DT_DATA_WRITEABLE
, DPL_KERNEL
);
322 // TODO: test if APM segments really are in the BIOS ROM area
323 // (especially the data segment)
327 // We ignore their length, and just set their segments to 64 kB which
328 // shouldn't cause any headaches
330 set_segment_descriptor(&gdt
[APM_CODE32_SEGMENT
],
331 gBiosBase
+ (info
.code32_segment_base
<< 4) - 0xe0000, 0xffff,
332 DT_CODE_READABLE
, DPL_KERNEL
);
333 set_segment_descriptor(&gdt
[APM_CODE16_SEGMENT
],
334 gBiosBase
+ (info
.code16_segment_base
<< 4) - 0xe0000, 0xffff,
335 DT_CODE_READABLE
, DPL_KERNEL
);
336 gdt
[APM_CODE16_SEGMENT
].d_b
= 0;
339 if ((info
.data_segment_base
<< 4) < 0xe0000) {
340 // use the BIOS data segment as data segment for APM
342 if (info
.data_segment_length
== 0) {
343 args
->platform_args
.apm
.data_segment_length
= B_PAGE_SIZE
344 - info
.data_segment_base
;
347 set_segment_descriptor(&gdt
[APM_DATA_SEGMENT
],
348 (addr_t
)gDmaAddress
+ (info
.data_segment_base
<< 4),
349 info
.data_segment_length
,
350 DT_DATA_WRITEABLE
, DPL_KERNEL
);
352 // use the BIOS area as data segment
353 set_segment_descriptor(&gdt
[APM_DATA_SEGMENT
],
354 gBiosBase
+ (info
.data_segment_base
<< 4) - 0xe0000, 0xffff,
355 DT_DATA_WRITEABLE
, DPL_KERNEL
);
359 // setup APM entry point
361 sAPMBiosEntry
.segment
= (APM_CODE32_SEGMENT
<< 3) | DPL_KERNEL
;
362 sAPMBiosEntry
.offset
= info
.code32_segment_offset
;
364 apm_driver_version(info
.version
);
366 if (apm_enable_power_management(APM_ALL_DEVICES
, true) != B_OK
)
367 dprintf("APM: cannot enable power management.\n");
368 if (apm_engage_power_management(APM_ALL_DEVICES
, true) != B_OK
)
369 dprintf("APM: cannot engage.\n");
371 register_kernel_daemon(apm_daemon
, NULL
, 10);
372 // run the daemon once every second
374 register_generic_syscall(APM_SYSCALLS
, apm_control
, 1, 0);