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 <safemode.h> 13 #include <boot/kernel_args.h> 14 15 16 #define TRACE_APM 17 #ifdef TRACE_APM 18 # define TRACE(x) dprintf x 19 #else 20 # define TRACE(x) ; 21 #endif 22 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 33 34 #define CARRY_FLAG 0x01 35 36 extern segment_descriptor *gGDT; 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 return user_memcpy(buffer, &info, sizeof(struct apm_battery_info)); 250 } 251 252 return B_BAD_VALUE; 253 } 254 255 256 // #pragma mark - 257 258 259 status_t 260 apm_shutdown(void) 261 { 262 if (!sAPMEnabled) 263 return B_NOT_SUPPORTED; 264 265 cpu_status state = disable_interrupts(); 266 267 status_t status = apm_set_state(APM_ALL_DEVICES, APM_POWER_STATE_OFF); 268 269 restore_interrupts(state); 270 return status; 271 } 272 273 274 status_t 275 apm_init(kernel_args *args) 276 { 277 const apm_info &info = args->platform_args.apm; 278 279 TRACE(("apm_init()\n")); 280 281 if ((info.version & 0xf) < 2) { 282 // no APM or connect failed 283 return B_ERROR; 284 } 285 286 TRACE((" code32: 0x%x, 0x%lx, length 0x%x\n", 287 info.code32_segment_base, info.code32_segment_offset, info.code32_segment_length)); 288 TRACE((" code16: 0x%x, length 0x%x\n", 289 info.code16_segment_base, info.code16_segment_length)); 290 TRACE((" data: 0x%x, length 0x%x\n", 291 info.data_segment_base, info.data_segment_length)); 292 293 // get APM setting - safemode settings override kernel settings 294 295 bool apm = false; 296 297 void *handle = load_driver_settings("kernel"); 298 if (handle != NULL) { 299 apm = get_driver_boolean_parameter(handle, "apm", false, false); 300 unload_driver_settings(handle); 301 } 302 303 handle = load_driver_settings(B_SAFEMODE_DRIVER_SETTINGS); 304 if (handle != NULL) { 305 apm = !get_driver_boolean_parameter(handle, B_SAFEMODE_DISABLE_APM, !apm, !apm); 306 unload_driver_settings(handle); 307 } 308 309 if (!apm) 310 return B_OK; 311 312 // Apparently, some broken BIOS try to access segment 0x40 for the BIOS 313 // data section - we make sure it can by setting up the GDT accordingly 314 // (the first 640kB are mapped as DMA area in arch_vm_init()). 315 addr_t biosData = (addr_t)gDmaAddress + 0x400; 316 317 set_segment_descriptor(&gGDT[BIOS_DATA_SEGMENT >> 3], 318 biosData, B_PAGE_SIZE - biosData, 319 DT_DATA_WRITEABLE, DPL_KERNEL); 320 321 // TODO: test if APM segments really are in the BIOS ROM area (especially the 322 // data segment) 323 324 // Setup APM GDTs 325 326 // We ignore their length, and just set their segments to 64 kB which 327 // shouldn't cause any headaches 328 329 set_segment_descriptor(&gGDT[APM_CODE32_SEGMENT >> 3], 330 gBiosBase + (info.code32_segment_base << 4) - 0xe0000, 0xffff, 331 DT_CODE_READABLE, DPL_KERNEL); 332 set_segment_descriptor(&gGDT[APM_CODE16_SEGMENT >> 3], 333 gBiosBase + (info.code16_segment_base << 4) - 0xe0000, 0xffff, 334 DT_CODE_READABLE, DPL_KERNEL); 335 gGDT[APM_CODE16_SEGMENT >> 3].d_b = 0; 336 // 16-bit segment 337 338 if ((info.data_segment_base << 4) < 0xe0000) { 339 // use the BIOS data segment as data segment for APM 340 341 if (info.data_segment_length == 0) { 342 args->platform_args.apm.data_segment_length = B_PAGE_SIZE 343 - info.data_segment_base; 344 } 345 346 set_segment_descriptor(&gGDT[APM_DATA_SEGMENT >> 3], 347 (addr_t)gDmaAddress + (info.data_segment_base << 4), 348 info.data_segment_length, 349 DT_DATA_WRITEABLE, DPL_KERNEL); 350 } else { 351 // use the BIOS area as data segment 352 set_segment_descriptor(&gGDT[APM_DATA_SEGMENT >> 3], 353 gBiosBase + (info.data_segment_base << 4) - 0xe0000, 0xffff, 354 DT_DATA_WRITEABLE, DPL_KERNEL); 355 } 356 357 // setup APM entry point 358 359 sAPMBiosEntry.segment = APM_CODE32_SEGMENT; 360 sAPMBiosEntry.offset = info.code32_segment_offset; 361 362 apm_driver_version(info.version); 363 364 if (apm_enable_power_management(APM_ALL_DEVICES, true) != B_OK) 365 dprintf("APM: cannot enable power management.\n"); 366 if (apm_engage_power_management(APM_ALL_DEVICES, true) != B_OK) 367 dprintf("APM: cannot engage.\n"); 368 369 register_kernel_daemon(apm_daemon, NULL, 10); 370 // run the daemon once every second 371 372 register_generic_syscall(APM_SYSCALLS, apm_control, 1, 0); 373 sAPMEnabled = true; 374 return B_OK; 375 } 376 377