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 { 48 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 * 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 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 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 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 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 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 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: %lx, flags: %lx\n", regs.eax, regs.flags); 189 190 return B_OK; 191 } 192 193 194 static void 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 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 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 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 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%lx, 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