headers/bsd: Add sys/queue.h.
[haiku.git] / src / system / kernel / arch / x86 / 32 / apm.cpp
blobc5174485da64d758adc96eb1818e4f91fe54dfe7
1 /*
2 * Copyright 2006-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
7 #include <KernelExport.h>
9 #include <apm.h>
10 #include <descriptors.h>
11 #include <generic_syscall.h>
12 #include <safemode.h>
13 #include <boot/kernel_args.h>
16 #define TRACE_APM
17 #ifdef TRACE_APM
18 # define TRACE(x) dprintf x
19 #else
20 # define TRACE(x) ;
21 #endif
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;
40 static struct {
41 uint32 offset;
42 uint16 segment;
43 } sAPMBiosEntry;
46 struct bios_regs {
47 bios_regs() : eax(0), ebx(0), ecx(0), edx(0), esi(0), flags(0) {}
48 uint32 eax;
49 uint32 ebx;
50 uint32 ecx;
51 uint32 edx;
52 uint32 esi;
53 uint32 flags;
57 #ifdef TRACE_APM
58 static const char *
59 apm_error(uint32 error)
61 switch (error >> 8) {
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";
83 default:
84 return "Unknown error";
87 #endif // TRACE_APM
90 static status_t
91 call_apm_bios(bios_regs *regs)
93 #if __GNUC__ < 4
94 // TODO: Fix this for GCC 4.3! The direct reference to sAPMBiosEntry
95 // in the asm below causes undefined references.
96 asm volatile(
97 "pushfl; "
98 "pushl %%ebp; "
99 "lcall *%%cs:sAPMBiosEntry; "
100 "popl %%ebp; "
101 "pushfl; "
102 "popl %%edi; "
103 "movl %%edi, %5; "
104 "popfl; "
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)
111 return B_ERROR;
113 return B_OK;
114 #else
115 return B_ERROR;
116 #endif
120 static status_t
121 apm_get_event(uint16 &event, uint16 &info)
123 bios_regs regs;
124 regs.eax = BIOS_APM_GET_EVENT;
126 if (call_apm_bios(&regs) != B_OK)
127 return B_ERROR;
129 event = regs.ebx & 0xffff;
130 info = regs.ecx & 0xffff;
131 return B_OK;
135 static status_t
136 apm_set_state(uint16 device, uint16 state)
138 bios_regs regs;
139 regs.eax = BIOS_APM_SET_STATE;
140 regs.ebx = device;
141 regs.ecx = state;
143 status_t status = call_apm_bios(&regs);
144 if (status == B_OK)
145 return B_OK;
147 TRACE(("apm_set_state() error: %s\n", apm_error(regs.eax)));
148 return status;
152 static status_t
153 apm_enable_power_management(uint16 device, bool enable)
155 bios_regs regs;
156 regs.eax = BIOS_APM_ENABLE;
157 regs.ebx = device;
158 regs.ecx = enable ? 0x01 : 0x00;
160 return call_apm_bios(&regs);
164 static status_t
165 apm_engage_power_management(uint16 device, bool engage)
167 bios_regs regs;
168 regs.eax = BIOS_APM_ENGAGE;
169 regs.ebx = device;
170 regs.ecx = engage ? 0x01 : 0x00;
172 return call_apm_bios(&regs);
176 status_t
177 apm_driver_version(uint16 version)
179 dprintf("version: %x\n", version);
180 bios_regs regs;
181 regs.eax = BIOS_APM_VERSION;
182 regs.ecx = version;
184 if (call_apm_bios(&regs) != B_OK)
185 return B_ERROR;
187 dprintf("eax: %lx, flags: %lx\n", regs.eax, regs.flags);
189 return B_OK;
193 static void
194 apm_daemon(void *arg, int iteration)
196 uint16 event;
197 uint16 info;
198 if (apm_get_event(event, info) != B_OK)
199 return;
201 dprintf("APM event: %x, info: %x\n", event, info);
205 static status_t
206 get_apm_battery_info(apm_battery_info *info)
208 bios_regs regs;
209 regs.eax = BIOS_APM_GET_POWER_STATUS;
210 regs.ebx = APM_ALL_DEVICES;
211 regs.ecx = 0;
213 status_t status = call_apm_bios(&regs);
214 if (status != B_OK)
215 return status;
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)
224 info->percent = -1;
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;
230 return B_OK;
234 static status_t
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))
240 return B_BAD_VALUE;
242 switch (function) {
243 case APM_GET_BATTERY_INFO:
244 status_t status = get_apm_battery_info(&info);
245 if (status < B_OK)
246 return status;
248 return user_memcpy(buffer, &info, sizeof(struct apm_battery_info));
251 return B_BAD_VALUE;
255 // #pragma mark -
258 status_t
259 apm_shutdown(void)
261 if (!sAPMEnabled)
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);
269 return status;
273 status_t
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
282 return B_ERROR;
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
294 bool apm = false;
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);
308 if (!apm)
309 return B_OK;
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)
325 // Setup APM GDTs
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;
337 // 16-bit segment
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);
351 } else {
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);
375 sAPMEnabled = true;
376 return B_OK;