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 void *gDmaAddress; 37 extern addr_t gBiosBase; 38 39 static bool sAPMEnabled = false; 40 static struct { 41 uint32 offset; 42 uint16 segment; 43 } sAPMBiosEntry; 44 45 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; 54 }; 55 56 57 #ifdef TRACE_APM 58 static const char * 59 apm_error(uint32 error) 60 { 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"; 82 83 default: 84 return "Unknown error"; 85 } 86 } 87 #endif // TRACE_APM 88 89 90 static status_t 91 call_apm_bios(bios_regs *regs) 92 { 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"); 109 110 if (regs->flags & CARRY_FLAG) 111 return B_ERROR; 112 113 return B_OK; 114 #else 115 return B_ERROR; 116 #endif 117 } 118 119 120 static status_t 121 apm_get_event(uint16 &event, uint16 &info) 122 { 123 bios_regs regs; 124 regs.eax = BIOS_APM_GET_EVENT; 125 126 if (call_apm_bios(®s) != B_OK) 127 return B_ERROR; 128 129 event = regs.ebx & 0xffff; 130 info = regs.ecx & 0xffff; 131 return B_OK; 132 } 133 134 135 static status_t 136 apm_set_state(uint16 device, uint16 state) 137 { 138 bios_regs regs; 139 regs.eax = BIOS_APM_SET_STATE; 140 regs.ebx = device; 141 regs.ecx = state; 142 143 status_t status = call_apm_bios(®s); 144 if (status == B_OK) 145 return B_OK; 146 147 TRACE(("apm_set_state() error: %s\n", apm_error(regs.eax))); 148 return status; 149 } 150 151 152 static status_t 153 apm_enable_power_management(uint16 device, bool enable) 154 { 155 bios_regs regs; 156 regs.eax = BIOS_APM_ENABLE; 157 regs.ebx = device; 158 regs.ecx = enable ? 0x01 : 0x00; 159 160 return call_apm_bios(®s); 161 } 162 163 164 static status_t 165 apm_engage_power_management(uint16 device, bool engage) 166 { 167 bios_regs regs; 168 regs.eax = BIOS_APM_ENGAGE; 169 regs.ebx = device; 170 regs.ecx = engage ? 0x01 : 0x00; 171 172 return call_apm_bios(®s); 173 } 174 175 176 status_t 177 apm_driver_version(uint16 version) 178 { 179 dprintf("version: %x\n", version); 180 bios_regs regs; 181 regs.eax = BIOS_APM_VERSION; 182 regs.ecx = version; 183 184 if (call_apm_bios(®s) != B_OK) 185 return B_ERROR; 186 187 dprintf("eax: %lx, flags: %lx\n", regs.eax, regs.flags); 188 189 return B_OK; 190 } 191 192 193 static void 194 apm_daemon(void *arg, int iteration) 195 { 196 uint16 event; 197 uint16 info; 198 if (apm_get_event(event, info) != B_OK) 199 return; 200 201 dprintf("APM event: %x, info: %x\n", event, info); 202 } 203 204 205 static status_t 206 get_apm_battery_info(apm_battery_info *info) 207 { 208 bios_regs regs; 209 regs.eax = BIOS_APM_GET_POWER_STATUS; 210 regs.ebx = APM_ALL_DEVICES; 211 regs.ecx = 0; 212 213 status_t status = call_apm_bios(®s); 214 if (status != B_OK) 215 return status; 216 217 uint16 lineStatus = (regs.ebx >> 8) & 0xff; 218 if (lineStatus == 0xff) 219 return B_NOT_SUPPORTED; 220 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; 225 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; 229 230 return B_OK; 231 } 232 233 234 static status_t 235 apm_control(const char *subsystem, uint32 function, 236 void *buffer, size_t bufferSize) 237 { 238 struct apm_battery_info info; 239 if (bufferSize != sizeof(struct apm_battery_info)) 240 return B_BAD_VALUE; 241 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; 247 248 return user_memcpy(buffer, &info, sizeof(struct apm_battery_info)); 249 } 250 251 return B_BAD_VALUE; 252 } 253 254 255 // #pragma mark - 256 257 258 status_t 259 apm_shutdown(void) 260 { 261 if (!sAPMEnabled) 262 return B_NOT_SUPPORTED; 263 264 cpu_status state = disable_interrupts(); 265 266 status_t status = apm_set_state(APM_ALL_DEVICES, APM_POWER_STATE_OFF); 267 268 restore_interrupts(state); 269 return status; 270 } 271 272 273 status_t 274 apm_init(kernel_args *args) 275 { 276 const apm_info &info = args->platform_args.apm; 277 278 TRACE(("apm_init()\n")); 279 280 if ((info.version & 0xf) < 2) { 281 // no APM or connect failed 282 return B_ERROR; 283 } 284 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)); 291 292 // get APM setting - safemode settings override kernel settings 293 294 bool apm = false; 295 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); 300 } 301 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); 306 } 307 308 if (!apm) 309 return B_OK; 310 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; 315 316 for (uint32 i = 0; i < args->num_cpus; i++) { 317 segment_descriptor* gdt = get_gdt(i); 318 319 set_segment_descriptor(&gdt[BIOS_DATA_SEGMENT], biosData, 320 B_PAGE_SIZE - biosData, DT_DATA_WRITEABLE, DPL_KERNEL); 321 322 // TODO: test if APM segments really are in the BIOS ROM area 323 // (especially the data segment) 324 325 // Setup APM GDTs 326 327 // We ignore their length, and just set their segments to 64 kB which 328 // shouldn't cause any headaches 329 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 338 339 if ((info.data_segment_base << 4) < 0xe0000) { 340 // use the BIOS data segment as data segment for APM 341 342 if (info.data_segment_length == 0) { 343 args->platform_args.apm.data_segment_length = B_PAGE_SIZE 344 - info.data_segment_base; 345 } 346 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); 356 } 357 } 358 359 // setup APM entry point 360 361 sAPMBiosEntry.segment = (APM_CODE32_SEGMENT << 3) | DPL_KERNEL; 362 sAPMBiosEntry.offset = info.code32_segment_offset; 363 364 apm_driver_version(info.version); 365 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"); 370 371 register_kernel_daemon(apm_daemon, NULL, 10); 372 // run the daemon once every second 373 374 register_generic_syscall(APM_SYSCALLS, apm_control, 1, 0); 375 sAPMEnabled = true; 376 return B_OK; 377 } 378 379