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 */
5
6
7 #include <KernelExport.h>
8
9 #include <apm.h>
10 #include <descriptors.h>
11 #include <generic_syscall.h>
12 #include <kernel.h>
13 #include <safemode.h>
14 #include <boot/kernel_args.h>
15
16
17 #define TRACE_APM
18 #ifdef TRACE_APM
19 # define TRACE(x) dprintf x
20 #else
21 # define TRACE(x) ;
22 #endif
23
24 #define APM_ERROR_DISABLED 0x01
25 #define APM_ERROR_DISCONNECTED 0x03
26 #define APM_ERROR_UNKNOWN_DEVICE 0x09
27 #define APM_ERROR_OUT_OF_RANGE 0x0a
28 #define APM_ERROR_DISENGAGED 0x0b
29 #define APM_ERROR_NOT_SUPPORTED 0x0c
30 #define APM_ERROR_RESUME_TIMER_DISABLED 0x0d
31 #define APM_ERROR_UNABLE_TO_ENTER_STATE 0x60
32 #define APM_ERROR_NO_EVENTS_PENDING 0x80
33 #define APM_ERROR_APM_NOT_PRESENT 0x86
34
35 #define CARRY_FLAG 0x01
36
37 extern void *gDmaAddress;
38 extern addr_t gBiosBase;
39
40 static bool sAPMEnabled = false;
41 static struct {
42 uint32 offset;
43 uint16 segment;
44 } sAPMBiosEntry;
45
46
47 struct bios_regs {
bios_regsbios_regs48 bios_regs() : eax(0), ebx(0), ecx(0), edx(0), esi(0), flags(0) {}
49 uint32 eax;
50 uint32 ebx;
51 uint32 ecx;
52 uint32 edx;
53 uint32 esi;
54 uint32 flags;
55 };
56
57
58 #ifdef TRACE_APM
59 static const char *
apm_error(uint32 error)60 apm_error(uint32 error)
61 {
62 switch (error >> 8) {
63 case APM_ERROR_DISABLED:
64 return "Power Management disabled";
65 case APM_ERROR_DISCONNECTED:
66 return "Interface disconnected";
67 case APM_ERROR_UNKNOWN_DEVICE:
68 return "Unrecognized device ID";
69 case APM_ERROR_OUT_OF_RANGE:
70 return "Parameter value out of range";
71 case APM_ERROR_DISENGAGED:
72 return "Interface not engaged";
73 case APM_ERROR_NOT_SUPPORTED:
74 return "Function not supported";
75 case APM_ERROR_RESUME_TIMER_DISABLED:
76 return "Resume timer disabled";
77 case APM_ERROR_UNABLE_TO_ENTER_STATE:
78 return "Unable to enter requested state";
79 case APM_ERROR_NO_EVENTS_PENDING:
80 return "No power management events pending";
81 case APM_ERROR_APM_NOT_PRESENT:
82 return "APM not present";
83
84 default:
85 return "Unknown error";
86 }
87 }
88 #endif // TRACE_APM
89
90
91 static status_t
call_apm_bios(bios_regs * regs)92 call_apm_bios(bios_regs *regs)
93 {
94 #if __GNUC__ < 4
95 // TODO: Fix this for GCC 4.3! The direct reference to sAPMBiosEntry
96 // in the asm below causes undefined references.
97 asm volatile(
98 "pushfl; "
99 "pushl %%ebp; "
100 "lcall *%%cs:sAPMBiosEntry; "
101 "popl %%ebp; "
102 "pushfl; "
103 "popl %%edi; "
104 "movl %%edi, %5; "
105 "popfl; "
106 : "=a" (regs->eax), "=b" (regs->ebx), "=c" (regs->ecx), "=d" (regs->edx),
107 "=S" (regs->esi), "=m" (regs->flags)
108 : "a" (regs->eax), "b" (regs->ebx), "c" (regs->ecx)
109 : "memory", "edi", "cc");
110
111 if (regs->flags & CARRY_FLAG)
112 return B_ERROR;
113
114 return B_OK;
115 #else
116 return B_ERROR;
117 #endif
118 }
119
120
121 static status_t
apm_get_event(uint16 & event,uint16 & info)122 apm_get_event(uint16 &event, uint16 &info)
123 {
124 bios_regs regs;
125 regs.eax = BIOS_APM_GET_EVENT;
126
127 if (call_apm_bios(®s) != B_OK)
128 return B_ERROR;
129
130 event = regs.ebx & 0xffff;
131 info = regs.ecx & 0xffff;
132 return B_OK;
133 }
134
135
136 static status_t
apm_set_state(uint16 device,uint16 state)137 apm_set_state(uint16 device, uint16 state)
138 {
139 bios_regs regs;
140 regs.eax = BIOS_APM_SET_STATE;
141 regs.ebx = device;
142 regs.ecx = state;
143
144 status_t status = call_apm_bios(®s);
145 if (status == B_OK)
146 return B_OK;
147
148 TRACE(("apm_set_state() error: %s\n", apm_error(regs.eax)));
149 return status;
150 }
151
152
153 static status_t
apm_enable_power_management(uint16 device,bool enable)154 apm_enable_power_management(uint16 device, bool enable)
155 {
156 bios_regs regs;
157 regs.eax = BIOS_APM_ENABLE;
158 regs.ebx = device;
159 regs.ecx = enable ? 0x01 : 0x00;
160
161 return call_apm_bios(®s);
162 }
163
164
165 static status_t
apm_engage_power_management(uint16 device,bool engage)166 apm_engage_power_management(uint16 device, bool engage)
167 {
168 bios_regs regs;
169 regs.eax = BIOS_APM_ENGAGE;
170 regs.ebx = device;
171 regs.ecx = engage ? 0x01 : 0x00;
172
173 return call_apm_bios(®s);
174 }
175
176
177 status_t
apm_driver_version(uint16 version)178 apm_driver_version(uint16 version)
179 {
180 dprintf("version: %x\n", version);
181 bios_regs regs;
182 regs.eax = BIOS_APM_VERSION;
183 regs.ecx = version;
184
185 if (call_apm_bios(®s) != B_OK)
186 return B_ERROR;
187
188 dprintf("eax: %x, flags: %x\n", regs.eax, regs.flags);
189
190 return B_OK;
191 }
192
193
194 static void
apm_daemon(void * arg,int iteration)195 apm_daemon(void *arg, int iteration)
196 {
197 uint16 event;
198 uint16 info;
199 if (apm_get_event(event, info) != B_OK)
200 return;
201
202 dprintf("APM event: %x, info: %x\n", event, info);
203 }
204
205
206 static status_t
get_apm_battery_info(apm_battery_info * info)207 get_apm_battery_info(apm_battery_info *info)
208 {
209 bios_regs regs;
210 regs.eax = BIOS_APM_GET_POWER_STATUS;
211 regs.ebx = APM_ALL_DEVICES;
212 regs.ecx = 0;
213
214 status_t status = call_apm_bios(®s);
215 if (status != B_OK)
216 return status;
217
218 uint16 lineStatus = (regs.ebx >> 8) & 0xff;
219 if (lineStatus == 0xff)
220 return B_NOT_SUPPORTED;
221
222 info->online = lineStatus != 0 && lineStatus != 2;
223 info->percent = regs.ecx & 0xff;
224 if (info->percent > 100 || info->percent < 0)
225 info->percent = -1;
226
227 info->time_left = info->percent >= 0 ? (int32)(regs.edx & 0xffff) : -1;
228 if (info->time_left & 0x8000)
229 info->time_left = (info->time_left & 0x7fff) * 60;
230
231 return B_OK;
232 }
233
234
235 static status_t
apm_control(const char * subsystem,uint32 function,void * buffer,size_t bufferSize)236 apm_control(const char *subsystem, uint32 function,
237 void *buffer, size_t bufferSize)
238 {
239 struct apm_battery_info info;
240 if (bufferSize != sizeof(struct apm_battery_info))
241 return B_BAD_VALUE;
242
243 switch (function) {
244 case APM_GET_BATTERY_INFO:
245 status_t status = get_apm_battery_info(&info);
246 if (status < B_OK)
247 return status;
248
249 if (buffer == NULL || !IS_USER_ADDRESS(buffer))
250 return B_BAD_ADDRESS;
251 return user_memcpy(buffer, &info, sizeof(struct apm_battery_info));
252 }
253
254 return B_BAD_VALUE;
255 }
256
257
258 // #pragma mark -
259
260
261 status_t
apm_shutdown(void)262 apm_shutdown(void)
263 {
264 if (!sAPMEnabled)
265 return B_NOT_SUPPORTED;
266
267 cpu_status state = disable_interrupts();
268
269 status_t status = apm_set_state(APM_ALL_DEVICES, APM_POWER_STATE_OFF);
270
271 restore_interrupts(state);
272 return status;
273 }
274
275
276 status_t
apm_init(kernel_args * args)277 apm_init(kernel_args *args)
278 {
279 const apm_info &info = args->platform_args.apm;
280
281 TRACE(("apm_init()\n"));
282
283 if ((info.version & 0xf) < 2) {
284 // no APM or connect failed
285 return B_ERROR;
286 }
287
288 TRACE((" code32: 0x%x, 0x%x, length 0x%x\n",
289 info.code32_segment_base, info.code32_segment_offset, info.code32_segment_length));
290 TRACE((" code16: 0x%x, length 0x%x\n",
291 info.code16_segment_base, info.code16_segment_length));
292 TRACE((" data: 0x%x, length 0x%x\n",
293 info.data_segment_base, info.data_segment_length));
294
295 // get APM setting - safemode settings override kernel settings
296
297 bool apm = false;
298
299 void *handle = load_driver_settings("kernel");
300 if (handle != NULL) {
301 apm = get_driver_boolean_parameter(handle, "apm", false, false);
302 unload_driver_settings(handle);
303 }
304
305 handle = load_driver_settings(B_SAFEMODE_DRIVER_SETTINGS);
306 if (handle != NULL) {
307 apm = !get_driver_boolean_parameter(handle, B_SAFEMODE_DISABLE_APM, !apm, !apm);
308 unload_driver_settings(handle);
309 }
310
311 if (!apm)
312 return B_OK;
313
314 // Apparently, some broken BIOS try to access segment 0x40 for the BIOS
315 // data section - we make sure it can by setting up the GDT accordingly
316 // (the first 640kB are mapped as DMA area in arch_vm_init()).
317 addr_t biosData = (addr_t)gDmaAddress + 0x400;
318
319 for (uint32 i = 0; i < args->num_cpus; i++) {
320 segment_descriptor* gdt = get_gdt(i);
321
322 set_segment_descriptor(&gdt[BIOS_DATA_SEGMENT], biosData,
323 B_PAGE_SIZE - biosData, DT_DATA_WRITEABLE, DPL_KERNEL);
324
325 // TODO: test if APM segments really are in the BIOS ROM area
326 // (especially the data segment)
327
328 // Setup APM GDTs
329
330 // We ignore their length, and just set their segments to 64 kB which
331 // shouldn't cause any headaches
332
333 set_segment_descriptor(&gdt[APM_CODE32_SEGMENT],
334 gBiosBase + (info.code32_segment_base << 4) - 0xe0000, 0xffff,
335 DT_CODE_READABLE, DPL_KERNEL);
336 set_segment_descriptor(&gdt[APM_CODE16_SEGMENT],
337 gBiosBase + (info.code16_segment_base << 4) - 0xe0000, 0xffff,
338 DT_CODE_READABLE, DPL_KERNEL);
339 gdt[APM_CODE16_SEGMENT].d_b = 0;
340 // 16-bit segment
341
342 if ((info.data_segment_base << 4) < 0xe0000) {
343 // use the BIOS data segment as data segment for APM
344
345 if (info.data_segment_length == 0) {
346 args->platform_args.apm.data_segment_length = B_PAGE_SIZE
347 - info.data_segment_base;
348 }
349
350 set_segment_descriptor(&gdt[APM_DATA_SEGMENT],
351 (addr_t)gDmaAddress + (info.data_segment_base << 4),
352 info.data_segment_length,
353 DT_DATA_WRITEABLE, DPL_KERNEL);
354 } else {
355 // use the BIOS area as data segment
356 set_segment_descriptor(&gdt[APM_DATA_SEGMENT],
357 gBiosBase + (info.data_segment_base << 4) - 0xe0000, 0xffff,
358 DT_DATA_WRITEABLE, DPL_KERNEL);
359 }
360 }
361
362 // setup APM entry point
363
364 sAPMBiosEntry.segment = (APM_CODE32_SEGMENT << 3) | DPL_KERNEL;
365 sAPMBiosEntry.offset = info.code32_segment_offset;
366
367 apm_driver_version(info.version);
368
369 if (apm_enable_power_management(APM_ALL_DEVICES, true) != B_OK)
370 dprintf("APM: cannot enable power management.\n");
371 if (apm_engage_power_management(APM_ALL_DEVICES, true) != B_OK)
372 dprintf("APM: cannot engage.\n");
373
374 register_kernel_daemon(apm_daemon, NULL, 10);
375 // run the daemon once every second
376
377 register_generic_syscall(APM_SYSCALLS, apm_control, 1, 0);
378 sAPMEnabled = true;
379 return B_OK;
380 }
381
382