[CMBATT]
[reactos.git] / reactos / drivers / bus / acpi / cmbatt / cmbatt.c
1 /*
2 * PROJECT: ReactOS ACPI-Compliant Control Method Battery
3 * LICENSE: BSD - See COPYING.ARM in the top level directory
4 * FILE: boot/drivers/bus/acpi/cmbatt/cmbatt.c
5 * PURPOSE: Main Initialization Code and IRP Handling
6 * PROGRAMMERS: ReactOS Portable Systems Group
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "cmbatt.h"
12
13 /* GLOBALS ********************************************************************/
14
15 ULONG CmBattDebug;
16 PCALLBACK_OBJECT CmBattPowerCallBackObject;
17 PVOID CmBattPowerCallBackRegistration;
18 UNICODE_STRING GlobalRegistryPath;
19 KTIMER CmBattWakeDpcTimerObject;
20 KDPC CmBattWakeDpcObject;
21 PDEVICE_OBJECT AcAdapterPdo;
22 LARGE_INTEGER CmBattWakeDpcDelay;
23
24 /* FUNCTIONS ******************************************************************/
25
26 VOID
27 NTAPI
28 CmBattPowerCallBack(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
29 IN ULONG Action,
30 IN ULONG Value)
31 {
32 BOOLEAN Cancelled;
33 PDEVICE_OBJECT DeviceObject;
34 if (CmBattDebug & 0x10)
35 DbgPrint("CmBattPowerCallBack: action: %d, value: %d \n", Action, Value);
36
37 /* Check if a transition is going to happen */
38 if (Action == PO_CB_SYSTEM_STATE_LOCK)
39 {
40 /* We have just re-entered S0: call the wake DPC in 10 seconds */
41 if (Value == 0)
42 {
43 if (CmBattDebug & 0x10)
44 DbgPrint("CmBattPowerCallBack: Calling CmBattWakeDpc after 10 seconds.\n");
45 Cancelled = KeSetTimer(&CmBattWakeDpcTimerObject, CmBattWakeDpcDelay, &CmBattWakeDpcObject);
46 if (CmBattDebug & 0x10)
47 DbgPrint("CmBattPowerCallBack: timerCanceled = %d.\n", Cancelled);
48 }
49 else if (Value == 0)
50 {
51 /* We are exiting the S0 state: loop all devices to set the delay flag */
52 if (CmBattDebug & 0x10)
53 DbgPrint("CmBattPowerCallBack: Delaying Notifications\n");
54 for (DeviceObject = DeviceExtension->DeviceObject;
55 DeviceObject;
56 DeviceObject = DeviceObject->NextDevice)
57 {
58 /* Set the delay flag */
59 DeviceExtension = DeviceObject->DeviceExtension;
60 DeviceExtension->DelayNotification = TRUE;
61 }
62 }
63 else if (CmBattDebug & 0x10)
64 {
65 /* Unknown value */
66 DbgPrint("CmBattPowerCallBack: unknown argument2 = %08x\n");
67 }
68 }
69 }
70
71 VOID
72 NTAPI
73 CmBattWakeDpc(IN PKDPC Dpc,
74 IN PCMBATT_DEVICE_EXTENSION FdoExtension,
75 IN PVOID SystemArgument1,
76 IN PVOID SystemArgument2)
77 {
78 PDEVICE_OBJECT CurrentObject;
79 BOOLEAN AcNotify = FALSE;
80 PCMBATT_DEVICE_EXTENSION DeviceExtension;
81 ULONG ArFlag;
82 if (CmBattDebug & 2) DbgPrint("CmBattWakeDpc: Entered.\n");
83
84 /* Loop all device objects */
85 for (CurrentObject = FdoExtension->DeviceObject;
86 CurrentObject;
87 CurrentObject = CurrentObject->NextDevice)
88 {
89 /* Turn delay flag off, we're back in S0 */
90 DeviceExtension = CurrentObject->DeviceExtension;
91 DeviceExtension->DelayNotification = 0;
92
93 /* Check if this is an AC adapter */
94 if (DeviceExtension->FdoType == CmBattAcAdapter)
95 {
96 /* Was there a pending notify? */
97 if (DeviceExtension->ArFlag & CMBATT_AR_NOTIFY)
98 {
99 /* We'll send a notify on the next pass */
100 AcNotify = TRUE;
101 DeviceExtension->ArFlag = 0;
102 if (CmBattDebug & 0x20)
103 DbgPrint("CmBattWakeDpc: AC adapter notified\n");
104 }
105 }
106 }
107
108 /* Loop the device objects again */
109 for (CurrentObject = FdoExtension->DeviceObject;
110 CurrentObject;
111 CurrentObject = CurrentObject->NextDevice)
112 {
113 /* Check if this is a battery */
114 DeviceExtension = CurrentObject->DeviceExtension;
115 if (DeviceExtension->FdoType == CmBattBattery)
116 {
117 /* Check what ARs are pending */
118 ArFlag = DeviceExtension->ArFlag;
119 if (CmBattDebug & 0x20)
120 DbgPrint("CmBattWakeDpc: Performing delayed ARs: %01x\n", ArFlag);
121
122 /* Insert notification, clear the lock value */
123 if (ArFlag & CMBATT_AR_INSERT) InterlockedExchange(&DeviceExtension->ArLockValue, 0);
124
125 /* Removal, clear the battery tag */
126 if (ArFlag & CMBATT_AR_REMOVE) DeviceExtension->Tag = 0;
127
128 /* Notification (or AC/DC adapter change from first pass above) */
129 if ((ArFlag & CMBATT_AR_NOTIFY) || (AcNotify))
130 {
131 /* Notify the class driver */
132 BatteryClassStatusNotify(DeviceExtension->ClassData);
133 }
134 }
135 }
136 }
137
138 VOID
139 NTAPI
140 CmBattNotifyHandler(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
141 IN ULONG NotifyValue)
142 {
143 ULONG ArFlag;
144 PCMBATT_DEVICE_EXTENSION FdoExtension;
145 PDEVICE_OBJECT DeviceObject;
146
147 if (CmBattDebug & (CMBATT_ACPI_ASSERT | CMBATT_PNP_INFO))
148 DbgPrint("CmBattNotifyHandler: CmBatt 0x%08x Type %d Number %d Notify Value: %x\n",
149 DeviceExtension,
150 DeviceExtension->FdoType,
151 DeviceExtension->DeviceId,
152 NotifyValue);
153
154 /* Check what kind of notification was received */
155 switch (NotifyValue)
156 {
157 /* ACPI Specification says is sends a "Bus Check" when power source changes */
158 case ACPI_BUS_CHECK:
159
160 /* We treat it as possible physical change */
161 DeviceExtension->ArFlag |= (CMBATT_AR_NOTIFY | CMBATT_AR_INSERT);
162 if ((DeviceExtension->Tag) &&
163 (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_WARNING)))
164 DbgPrint("CmBattNotifyHandler: Received battery #%x insertion, but tag was not invalid.\n",
165 DeviceExtension->DeviceId);
166 break;
167
168 /* Status of the battery has changed */
169 case ACPI_BATT_NOTIFY_STATUS:
170
171 /* All we'll do is notify the class driver */
172 DeviceExtension->ArFlag |= CMBATT_AR_NOTIFY;
173 break;
174
175 /* Information on the battery has changed, such as physical presence */
176 case ACPI_DEVICE_CHECK:
177 case ACPI_BATT_NOTIFY_INFO:
178
179 /* Reset all state and let the class driver re-evaluate it all */
180 DeviceExtension->ArFlag |= (CMBATT_AR_NOTIFY |
181 CMBATT_AR_INSERT |
182 CMBATT_AR_REMOVE);
183 break;
184
185 default:
186
187 if (CmBattDebug & CMBATT_PNP_INFO)
188 DbgPrint("CmBattNotifyHandler: Unknown Notify Value: %x\n", NotifyValue);
189 }
190
191 /* Check if we're supposed to delay the notification till later */
192 if (DeviceExtension->DelayNotification)
193 {
194 /* We'll handle this when we get a status query later on */
195 if (CmBattDebug & CMBATT_PNP_INFO)
196 DbgPrint("CmBattNotifyHandler: Notification delayed: ARs = %01x\n",
197 DeviceExtension->ArFlag);
198 return;
199 }
200
201 /* We're going to handle this now */
202 if (CmBattDebug & CMBATT_PNP_INFO)
203 DbgPrint("CmBattNotifyHandler: Performing ARs: %01x\n", DeviceExtension->ArFlag);
204
205 /* Check if this is a battery or AC adapter notification */
206 if (DeviceExtension->FdoType == CmBattBattery)
207 {
208 /* Reset the current trip point */
209 DeviceExtension->TripPointValue = BATTERY_UNKNOWN_CAPACITY;
210
211 /* Check what ARs have to be done */
212 ArFlag = DeviceExtension->ArFlag;
213
214 /* New battery inserted, reset lock value */
215 if (ArFlag & CMBATT_AR_INSERT) InterlockedExchange(&DeviceExtension->ArLockValue, 0);
216
217 /* Check if the battery may have been removed */
218 if (ArFlag & CMBATT_AR_REMOVE) DeviceExtension->Tag = 0;
219
220 /* Check if there's been any sort of change to the battery */
221 if (ArFlag & CMBATT_AR_NOTIFY)
222 {
223 /* We'll probably end up re-evaluating _BIF and _BST */
224 DeviceExtension->NotifySent = TRUE;
225 BatteryClassStatusNotify(DeviceExtension->ClassData);
226 }
227 }
228 else
229 {
230 /* The only known notification is AC/DC change */
231 if (DeviceExtension->ArFlag & CMBATT_AR_NOTIFY)
232 {
233 for (DeviceObject = DeviceExtension->FdoDeviceObject->DriverObject->DeviceObject;
234 DeviceObject;
235 DeviceObject = DeviceObject->NextDevice)
236 {
237 /* Is this a battery? */
238 FdoExtension = DeviceObject->DeviceExtension;
239 if (FdoExtension->FdoType == CmBattBattery)
240 {
241 /* Send a notification to the class driver */
242 FdoExtension->NotifySent = TRUE;
243 BatteryClassStatusNotify(FdoExtension->ClassData);
244 }
245 }
246 }
247 }
248
249 /* ARs have been processed */
250 DeviceExtension->ArFlag = 0;
251 }
252
253 VOID
254 NTAPI
255 CmBattUnload(IN PDRIVER_OBJECT DriverObject)
256 {
257 if (CmBattDebug & CMBATT_GENERIC_INFO) DPRINT("CmBattUnload: \n");
258
259 /* Check if we have a registered power callback */
260 if (CmBattPowerCallBackObject)
261 {
262 /* Get rid of it */
263 ExUnregisterCallback(CmBattPowerCallBackRegistration);
264 ObfDereferenceObject(CmBattPowerCallBackObject);
265 }
266
267 /* Free the registry buffer if it exists */
268 if (GlobalRegistryPath.Buffer) ExFreePool(GlobalRegistryPath.Buffer);
269
270 /* Make sure we don't still have references to the DO */
271 if ((DriverObject->DeviceObject) && (CmBattDebug & CMBATT_GENERIC_WARNING))
272 {
273 DbgPrint("Unload called before all devices removed.\n");
274 }
275 }
276
277 NTSTATUS
278 NTAPI
279 CmBattVerifyStaticInfo(PCMBATT_DEVICE_EXTENSION DeviceExtension,
280 ULONG BatteryTag)
281 {
282 UNIMPLEMENTED;
283 return STATUS_NOT_IMPLEMENTED;
284 }
285
286 NTSTATUS
287 NTAPI
288 CmBattOpenClose(IN PDEVICE_OBJECT DeviceObject,
289 IN PIRP Irp)
290 {
291 NTSTATUS Status = STATUS_SUCCESS;
292 PIO_STACK_LOCATION IoStackLocation;
293 UCHAR Major;
294 ULONG Count;
295 PCMBATT_DEVICE_EXTENSION DeviceExtension;
296 PAGED_CODE();
297 if (CmBattDebug & CMBATT_GENERIC_INFO) DPRINT("CmBattOpenClose\n");
298
299 /* Grab the device extension and lock it */
300 DeviceExtension = DeviceObject->DeviceExtension;
301 ExAcquireFastMutex(&DeviceExtension->FastMutex);
302
303 /* Check if someone is trying to open a device that doesn't exist yet */
304 Count = DeviceExtension->HandleCount;
305 if (Count == 0xFFFFFFFF)
306 {
307 /* Fail the request */
308 Status = STATUS_NO_SUCH_DEVICE;
309 if (CmBattDebug & CMBATT_PNP_INFO)
310 {
311 DbgPrint("CmBattOpenClose: Failed (UID = %x)(device being removed).\n",
312 DeviceExtension->Tag);
313 }
314 goto Complete;
315 }
316
317 /* Check if this is an open or close */
318 IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
319 Major = IoStackLocation->MajorFunction;
320 if (Major == IRP_MJ_CREATE)
321 {
322 /* Increment the open count */
323 DeviceExtension->HandleCount = Count + 1;
324 if (CmBattDebug & CMBATT_PNP_INFO)
325 {
326 DbgPrint("CmBattOpenClose: Open (DeviceNumber = %x)(count = %x).\n",
327 DeviceExtension->DeviceId, Count + 1);
328 }
329 }
330 else if (Major == IRP_MJ_CLOSE)
331 {
332 /* Decrement the open count */
333 DeviceExtension->HandleCount = Count - 1;
334 if (CmBattDebug & CMBATT_PNP_INFO)
335 {
336 DbgPrint("CmBattOpenClose: Close (DeviceNumber = %x)(count = %x).\n",
337 DeviceExtension->DeviceId, Count + 1);
338 }
339 }
340
341 Complete:
342 /* Release lock and complete request */
343 ExReleaseFastMutex(&DeviceExtension->FastMutex);
344 Irp->IoStatus.Status = Status;
345 IofCompleteRequest(Irp, IO_NO_INCREMENT);
346 return Status;
347 }
348
349 NTSTATUS
350 NTAPI
351 CmBattIoctl(PDEVICE_OBJECT DeviceObject,
352 PIRP Irp)
353 {
354 UNIMPLEMENTED;
355 return STATUS_NOT_IMPLEMENTED;
356 }
357
358 NTSTATUS
359 NTAPI
360 CmBattQueryTag(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
361 OUT PULONG Tag)
362 {
363 PDEVICE_OBJECT PdoDevice;
364 ULONG StaData;
365 ULONG NewTag;
366 NTSTATUS Status;
367 PAGED_CODE();
368 if (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_INFO))
369 DbgPrint("CmBattQueryTag - Tag (%d), Battery %x, Device %d\n",
370 *Tag, DeviceExtension, DeviceExtension->DeviceId);
371
372 /* Get PDO and clear notification flag */
373 PdoDevice = DeviceExtension->PdoDeviceObject;
374 DeviceExtension->NotifySent = 0;
375
376 /* Get _STA from PDO (we need the machine status, not the battery status) */
377 Status = CmBattGetStaData(PdoDevice, &StaData);
378 if (NT_SUCCESS(Status))
379 {
380 /* Is a battery present? */
381 if (StaData & ACPI_STA_BATTERY_PRESENT)
382 {
383 /* Do we not have a tag yet? */
384 if (!DeviceExtension->Tag)
385 {
386 /* Set the new tag value, reset tags if we reached the maximum */
387 NewTag = DeviceExtension->TagData;
388 if (DeviceExtension->TagData++ == 0xFFFFFFFF) NewTag = 1;
389 DeviceExtension->Tag = NewTag;
390 if (CmBattDebug & CMBATT_GENERIC_INFO)
391 DbgPrint("CmBattQueryTag - New Tag: (%d)\n", DeviceExtension->Tag);
392
393 /* Reset trip point data */
394 DeviceExtension->TripPointOld = 0;
395 DeviceExtension->TripPointValue = BATTERY_UNKNOWN_CAPACITY;
396
397 /* Clear AR lock and set new interrupt time */
398 InterlockedExchange(&DeviceExtension->ArLockValue, 0);
399 DeviceExtension->InterruptTime = KeQueryInterruptTime();
400 }
401 }
402 else
403 {
404 /* No battery, so no tag */
405 DeviceExtension->Tag = 0;
406 Status = STATUS_NO_SUCH_DEVICE;
407 }
408 }
409
410 /* Return the tag and status result */
411 *Tag = DeviceExtension->Tag;
412 if (CmBattDebug & CMBATT_ACPI_WARNING)
413 DbgPrint("CmBattQueryTag: Returning Tag: 0x%x, status 0x%x\n", *Tag, Status);
414 return Status;
415 }
416
417 NTSTATUS
418 NTAPI
419 CmBattDisableStatusNotify(IN PCMBATT_DEVICE_EXTENSION DeviceExtension)
420 {
421 NTSTATUS Status;
422 PAGED_CODE();
423 if (CmBattDebug & 0xA) DbgPrint("CmBattDisableStatusNotify\n");
424
425 /* Do we have a trip point */
426 if (DeviceExtension->TripPointSet)
427 {
428 /* Is there a current value set? */
429 if (DeviceExtension->TripPointValue)
430 {
431 /* Reset it back to 0 */
432 DeviceExtension->TripPointValue = 0;
433 Status = CmBattSetTripPpoint(DeviceExtension, 0);
434 if (!NT_SUCCESS(Status))
435 {
436 /* If it failed, set unknown/invalid value */
437 DeviceExtension->TripPointValue = BATTERY_UNKNOWN_CAPACITY;
438 if (CmBattDebug & 8)
439 DbgPrint("CmBattDisableStatusNotify: SetTripPoint failed - %x\n", Status);
440 }
441 }
442 else
443 {
444 /* No trip point set, so this is a successful no-op */
445 Status = STATUS_SUCCESS;
446 }
447 }
448 else
449 {
450 /* Nothing we can do */
451 Status = STATUS_OBJECT_NAME_NOT_FOUND;
452 }
453
454 /* Return status */
455 return Status;
456 }
457
458 NTSTATUS
459 NTAPI
460 CmBattSetStatusNotify(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
461 IN ULONG BatteryTag,
462 IN PBATTERY_NOTIFY BatteryNotify)
463 {
464 NTSTATUS Status;
465 ACPI_BST_DATA BstData;
466 ULONG Capacity, NewTripPoint, TripPoint, DesignVoltage;
467 BOOLEAN Charging;
468 PAGED_CODE();
469 if (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_INFO))
470 DbgPrint("CmBattSetStatusNotify: Tag (%d) Target(0x%x)\n",
471 BatteryTag, BatteryNotify->LowCapacity);
472
473 /* Update any ACPI evaluations */
474 Status = CmBattVerifyStaticInfo(DeviceExtension, BatteryTag);
475 if (!NT_SUCCESS(Status)) return Status;
476
477 /* Trip point not supported, fail */
478 if (!DeviceExtension->TripPointSet) return STATUS_OBJECT_NAME_NOT_FOUND;
479
480 /* Are both capacities known? */
481 if ((BatteryNotify->HighCapacity == BATTERY_UNKNOWN_CAPACITY) ||
482 (BatteryNotify->LowCapacity == BATTERY_UNKNOWN_CAPACITY))
483 {
484 /* We can't set trip points without these */
485 if (CmBattDebug & CMBATT_GENERIC_WARNING)
486 DbgPrint("CmBattSetStatusNotify: Failing request because of BATTERY_UNKNOWN_CAPACITY.\n");
487 return STATUS_NOT_SUPPORTED;
488 }
489
490 /* Is the battery charging? */
491 Charging = DeviceExtension->BstData.State & ACPI_BATT_STAT_CHARGING;
492 if (Charging)
493 {
494 /* Then the trip point is when we hit the cap */
495 Capacity = BatteryNotify->HighCapacity;
496 NewTripPoint = BatteryNotify->HighCapacity;
497 }
498 else
499 {
500 /* Otherwise it's when we discharge to the bottom */
501 Capacity = BatteryNotify->LowCapacity;
502 NewTripPoint = BatteryNotify->LowCapacity;
503 }
504
505 /* Do we have data in Amps or Watts? */
506 if (DeviceExtension->BifData.PowerUnit == ACPI_BATT_POWER_UNIT_AMPS)
507 {
508 /* We need the voltage to do the conversion */
509 DesignVoltage = DeviceExtension->BifData.DesignVoltage;
510 if ((DesignVoltage != BATTERY_UNKNOWN_VOLTAGE) && (DesignVoltage))
511 {
512 /* Convert from mAh into Ah */
513 TripPoint = 1000 * NewTripPoint;
514 if (Charging)
515 {
516 /* Scale the high trip point */
517 NewTripPoint = (TripPoint + 500) / DesignVoltage + ((TripPoint + 500) % DesignVoltage != 0);
518 }
519 else
520 {
521 /* Scale the low trip point */
522 NewTripPoint = (TripPoint - 500) / DesignVoltage - ((TripPoint - 500) % DesignVoltage == 0);
523 }
524 }
525 else
526 {
527 /* Without knowing the voltage, Amps are not enough data on consumption */
528 Status = STATUS_NOT_SUPPORTED;
529 if (CmBattDebug & CMBATT_ACPI_WARNING)
530 DbgPrint("CmBattSetStatusNotify: Can't calculate BTP, DesignVoltage = 0x%08x\n",
531 DesignVoltage);
532 }
533 }
534 else if (Charging)
535 {
536 /* Make it trip just one past the charge cap */
537 ++NewTripPoint;
538 }
539 else if (NewTripPoint > 0)
540 {
541 /* Make it trip just one below the drain cap */
542 --NewTripPoint;
543 }
544
545 /* Do we actually have a new trip point? */
546 if (NewTripPoint == DeviceExtension->TripPointValue)
547 {
548 /* No, so there is no work to be done */
549 if (CmBattDebug & CMBATT_GENERIC_STATUS)
550 DbgPrint("CmBattSetStatusNotify: Keeping original setting: %X\n", DeviceExtension->TripPointValue);
551 return STATUS_SUCCESS;
552 }
553
554 /* Set the trip point with ACPI and check for success */
555 DeviceExtension->TripPointValue = NewTripPoint;
556 Status = CmBattSetTripPpoint(DeviceExtension, NewTripPoint);
557 if (!(NewTripPoint) && (Capacity)) Status = STATUS_NOT_SUPPORTED;
558 if (!NT_SUCCESS(Status))
559 {
560 /* We failed to set the trip point, or there wasn't one settable */
561 DeviceExtension->TripPointValue = BATTERY_UNKNOWN_CAPACITY;
562 if (CmBattDebug & (CMBATT_GENERIC_WARNING | CMBATT_ACPI_WARNING))
563 DbgPrint("CmBattSetStatusNotify: SetTripPoint failed - %x\n", Status);
564 return Status;
565 }
566
567 /* Read the new BST data to see the latest state */
568 Status = CmBattGetBstData(DeviceExtension, &BstData);
569 if (!NT_SUCCESS(Status))
570 {
571 /* We'll return failure to the caller */
572 if (CmBattDebug & (CMBATT_GENERIC_WARNING | CMBATT_ACPI_WARNING))
573 DbgPrint("CmBattSetStatusNotify: GetBstData - %x\n", Status);
574 }
575 else if ((Charging) && (BstData.RemainingCapacity >= NewTripPoint))
576 {
577 /* We are charging and our capacity is past the trip point, so trip now */
578 if (CmBattDebug & CMBATT_GENERIC_WARNING)
579 DbgPrint("CmBattSetStatusNotify: Trip point already crossed (1): TP = %08x, remaining capacity = %08x\n",
580 NewTripPoint, BstData.RemainingCapacity);
581 CmBattNotifyHandler(DeviceExtension, ACPI_BATT_NOTIFY_STATUS);
582 }
583 else if ((BstData.RemainingCapacity) && (Capacity))
584 {
585 /* We are discharging, and our capacity is below the trip point, trip now */
586 if (CmBattDebug & CMBATT_GENERIC_WARNING)
587 DbgPrint("CmBattSetStatusNotify: Trip point already crossed (1): TP = %08x, remaining capacity = %08x\n",
588 NewTripPoint, BstData.RemainingCapacity);
589 CmBattNotifyHandler(DeviceExtension, ACPI_BATT_NOTIFY_STATUS);
590 }
591
592 /* All should've went well if we got here, unless BST failed... return! */
593 if (CmBattDebug & CMBATT_GENERIC_STATUS)
594 DbgPrint("CmBattSetStatusNotify: Want %X CurrentCap %X\n",
595 Capacity, DeviceExtension->RemainingCapacity);
596 if (CmBattDebug & CMBATT_ACPI_WARNING)
597 DbgPrint("CmBattSetStatusNotify: Set to: [%#08lx][%#08lx][%#08lx] Status %x\n",
598 BatteryNotify->PowerState,
599 BatteryNotify->LowCapacity,
600 BatteryNotify->HighCapacity);
601 return Status;
602 }
603
604 NTSTATUS
605 NTAPI
606 CmBattGetBatteryStatus(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
607 IN ULONG Tag)
608 {
609 ULONG PsrData = 0;
610 NTSTATUS Status;
611 ULONG BstState;
612 ULONG DesignVoltage, PresentRate, RemainingCapacity;
613 PAGED_CODE();
614 if (CmBattDebug & CMBATT_GENERIC_INFO)
615 DbgPrint("CmBattGetBatteryStatus - CmBatt (%08x) Tag (%d)\n", DeviceExtension, Tag);
616
617 /* Validate ACPI data */
618 Status = CmBattVerifyStaticInfo(DeviceExtension, Tag);
619 if (!NT_SUCCESS(Status)) return Status;
620
621 /* Check for delayed status notifications */
622 if (DeviceExtension->DelayNotification)
623 {
624 /* Process them now and don't do any other work */
625 CmBattNotifyHandler(DeviceExtension, ACPI_BATT_NOTIFY_STATUS);
626 return Status;
627 }
628
629 /* Get _BST from ACPI */
630 Status = CmBattGetBstData(DeviceExtension, &DeviceExtension->BstData);
631 if (!NT_SUCCESS(Status))
632 {
633 /* Fail */
634 InterlockedExchange(&DeviceExtension->ArLockValue, 0);
635 return Status;
636 }
637
638 /* Clear current BST information */
639 DeviceExtension->State = 0;
640 DeviceExtension->RemainingCapacity = 0;
641 DeviceExtension->PresentVoltage = 0;
642 DeviceExtension->Rate = 0;
643
644 /* Get battery state */
645 BstState = DeviceExtension->BstData.State;
646
647 /* Is the battery both charging and discharging? */
648 if ((BstState & ACPI_BATT_STAT_DISCHARG) && (BstState & ACPI_BATT_STAT_CHARGING) &&
649 (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_WARNING)))
650 DbgPrint("************************ ACPI BIOS BUG ********************\n* "
651 "CmBattGetBatteryStatus: Invalid state: _BST method returned 0x%08x for Battery State.\n"
652 "* One battery cannot be charging and discharging at the same time.\n",
653 BstState);
654
655 /* Is the battery discharging? */
656 if (BstState & ACPI_BATT_STAT_DISCHARG)
657 {
658 /* Set power state and check if it just started discharging now */
659 DeviceExtension->State |= BATTERY_DISCHARGING;
660 if (!(DeviceExtension->State & ACPI_BATT_STAT_DISCHARG))
661 {
662 /* Remember the time when the state changed */
663 DeviceExtension->InterruptTime = KeQueryInterruptTime();
664 }
665 }
666 else if (BstState & ACPI_BATT_STAT_CHARGING)
667 {
668 /* Battery is charging, update power state */
669 DeviceExtension->State |= (BATTERY_CHARGING | BATTERY_POWER_ON_LINE);
670 }
671
672 /* Is the battery in a critical state? */
673 if (BstState & ACPI_BATT_STAT_CRITICAL) DeviceExtension->State |= BATTERY_CRITICAL;
674
675 /* Read the voltage data */
676 DeviceExtension->PresentVoltage = DeviceExtension->BstData.PresentVoltage;
677
678 /* Check if we have an A/C adapter */
679 if (AcAdapterPdo)
680 {
681 /* Query information on it */
682 CmBattGetPsrData(AcAdapterPdo, &PsrData);
683 }
684 else
685 {
686 /* Otherwise, check if the battery is charging */
687 if (BstState & ACPI_BATT_STAT_CHARGING)
688 {
689 /* Then we'll assume there's a charger */
690 PsrData = 1;
691 }
692 else
693 {
694 /* Assume no charger */
695 PsrData = 0;
696 }
697 }
698
699 /* Is there a charger? */
700 if (PsrData)
701 {
702 /* Set the power state flag to reflect this */
703 DeviceExtension->State |= BATTERY_POWER_ON_LINE;
704 if (CmBattDebug & (CMBATT_GENERIC_INFO | CMBATT_GENERIC_STATUS))
705 DbgPrint("CmBattGetBatteryStatus: AC adapter is connected\n");
706 }
707 else if (CmBattDebug & (CMBATT_GENERIC_INFO | CMBATT_GENERIC_STATUS))
708 {
709 DbgPrint("CmBattGetBatteryStatus: AC adapter is NOT connected\n");
710 }
711
712 /* Get some data we'll need */
713 DesignVoltage = DeviceExtension->BifData.DesignVoltage;
714 PresentRate = DeviceExtension->BstData.PresentRate;
715 RemainingCapacity = DeviceExtension->BstData.RemainingCapacity;
716
717 /* Check if we have battery data in Watts instead of Amps */
718 if (DeviceExtension->BifData.PowerUnit == ACPI_BATT_POWER_UNIT_WATTS)
719 {
720 /* Get the data from the BST */
721 DeviceExtension->RemainingCapacity = RemainingCapacity;
722 DeviceExtension->Rate = PresentRate;
723
724 /* Check if the rate is invalid */
725 if (PresentRate > CM_MAX_VALUE)
726 {
727 /* Set an unknown rate and don't touch the old value */
728 DeviceExtension->Rate = BATTERY_UNKNOWN_RATE;
729 if ((PresentRate != CM_UNKNOWN_VALUE) && (CmBattDebug & CMBATT_ACPI_WARNING))
730 {
731 DbgPrint("CmBattGetBatteryStatus - Rate is greater than CM_MAX_VALUE\n");
732 DbgPrint("---------------------- PresentRate = 0x%08x\n", PresentRate);
733 }
734 }
735 }
736 else if ((DesignVoltage != CM_UNKNOWN_VALUE) && (DesignVoltage))
737 {
738 /* We have voltage data, what about capacity? */
739 if (RemainingCapacity == CM_UNKNOWN_VALUE)
740 {
741 /* Unable to calculate it */
742 DeviceExtension->RemainingCapacity = BATTERY_UNKNOWN_CAPACITY;
743 if (CmBattDebug & CMBATT_ACPI_WARNING)
744 {
745 DbgPrint("CmBattGetBatteryStatus - Can't calculate RemainingCapacity \n");
746 DbgPrint("---------------------- RemainingCapacity = CM_UNKNOWN_VALUE\n");
747 }
748 }
749 else
750 {
751 /* Compute the capacity with the information we have */
752 DeviceExtension->RemainingCapacity = (DesignVoltage * RemainingCapacity + 500) / 1000;
753 }
754
755 /* Check if we have a rate */
756 if (PresentRate != CM_UNKNOWN_VALUE)
757 {
758 /* Make sure the rate isn't too large */
759 if (PresentRate > (-500 / DesignVoltage))
760 {
761 /* It is, so set unknown state */
762 DeviceExtension->Rate = BATTERY_UNKNOWN_RATE;
763 if (CmBattDebug & CMBATT_ACPI_WARNING)
764 {
765 DbgPrint("CmBattGetBatteryStatus - Can't calculate Rate \n");
766 DbgPrint("---------------------- Overflow: PresentRate = 0x%08x\n", PresentRate);
767 }
768 }
769
770 /* Compute the rate */
771 DeviceExtension->Rate = (PresentRate * DesignVoltage + 500) / 1000;
772 }
773 else
774 {
775 /* We don't have a rate, so set unknown value */
776 DeviceExtension->Rate = BATTERY_UNKNOWN_RATE;
777 if (CmBattDebug & CMBATT_ACPI_WARNING)
778 {
779 DbgPrint("CmBattGetBatteryStatus - Can't calculate Rate \n");
780 DbgPrint("---------------------- Present Rate = CM_UNKNOWN_VALUE\n");
781 }
782 }
783 }
784 else
785 {
786 /* We have no rate, and no capacity, set unknown values */
787 DeviceExtension->Rate = BATTERY_UNKNOWN_RATE;
788 DeviceExtension->RemainingCapacity = BATTERY_UNKNOWN_CAPACITY;
789 if (CmBattDebug & CMBATT_ACPI_WARNING)
790 {
791 DbgPrint("CmBattGetBatteryStatus - Can't calculate RemainingCapacity and Rate \n");
792 DbgPrint("---------------------- DesignVoltage = 0x%08x\n", DesignVoltage);
793 }
794 }
795
796 /* Check if we have an unknown rate */
797 if (DeviceExtension->Rate == BATTERY_UNKNOWN_RATE)
798 {
799 /* The battery is discharging but we don't know by how much... this is bad! */
800 if ((BstState & ACPI_BATT_STAT_DISCHARG) &&
801 (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_WARNING)))
802 DbgPrint("CmBattGetBatteryStatus: battery rate is unkown when battery is not charging!\n");
803 }
804 else if (DeviceExtension->State & BATTERY_DISCHARGING)
805 {
806 /* The battery is discharging, so treat the rate as a negative rate */
807 DeviceExtension->Rate = -DeviceExtension->Rate;
808 }
809 else if (!(DeviceExtension->State & BATTERY_CHARGING) && (DeviceExtension->Rate))
810 {
811 /* We are not charging, not discharging, but have a rate? Ignore it! */
812 if (CmBattDebug & CMBATT_GENERIC_WARNING)
813 DbgPrint("CmBattGetBatteryStatus: battery is not charging or discharging, but rate = %x\n",
814 DeviceExtension->Rate);
815 DeviceExtension->Rate = 0;
816 }
817
818 /* Done */
819 return STATUS_SUCCESS;
820 }
821
822 NTSTATUS
823 NTAPI
824 CmBattQueryInformation(IN PCMBATT_DEVICE_EXTENSION FdoExtension,
825 IN ULONG Tag,
826 IN BATTERY_QUERY_INFORMATION_LEVEL InfoLevel,
827 IN OPTIONAL LONG AtRate,
828 IN PVOID Buffer,
829 IN ULONG BufferLength,
830 OUT PULONG ReturnedLength)
831 {
832 NTSTATUS Status;
833 PVOID QueryData = NULL;
834 ULONG QueryLength = 0;
835 ULONG RemainingTime = 0;
836 ANSI_STRING TempString;
837 UNICODE_STRING TempString2;
838 WCHAR InfoBuffer[256];
839 WCHAR TempBuffer[256];
840 UNICODE_STRING InfoString;
841 ULONG RemainingCapacity;
842 BATTERY_REPORTING_SCALE BatteryReportingScale[2];
843 LONG Rate;
844 PAGED_CODE();
845 if (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_INFO))
846 DbgPrint("CmBattQueryInformation - Tag (%d) Device %d, Informationlevel %d\n",
847 Tag,
848 FdoExtension->DeviceId,
849 InfoLevel);
850
851 /* Check ACPI Data */
852 Status = CmBattVerifyStaticInfo(FdoExtension, Tag);
853 if (!NT_SUCCESS(Status)) return Status;
854
855 /* Check what caller wants */
856 switch (InfoLevel)
857 {
858 case BatteryInformation:
859 /* Just return our static information */
860 QueryData = &FdoExtension->BatteryInformation;
861 QueryLength = sizeof(BATTERY_INFORMATION);
862 break;
863
864 case BatteryGranularityInformation:
865
866 /* Return our static information, we have two scales */
867 BatteryReportingScale[0].Granularity = FdoExtension->BatteryCapacityGranularity1;
868 BatteryReportingScale[0].Capacity = FdoExtension->BatteryInformation.DefaultAlert1;
869 BatteryReportingScale[1].Granularity = FdoExtension->BatteryCapacityGranularity2;
870 BatteryReportingScale[1].Capacity = FdoExtension->BatteryInformation.DesignedCapacity;
871 QueryData = BatteryReportingScale;
872 QueryLength = sizeof(BATTERY_REPORTING_SCALE) * 2;
873 break;
874
875 case BatteryEstimatedTime:
876
877 /* Check if it's been more than 2 1/2 minutes since the last change */
878 if ((KeQueryInterruptTime() - 150000000) > (FdoExtension->InterruptTime))
879 {
880 /* Get new battery status */
881 CmBattGetBatteryStatus(FdoExtension, FdoExtension->Tag);
882
883 /* If the caller didn't specify a rate, use our static one */
884 Rate = AtRate;
885 if (!Rate) Rate = FdoExtension->Rate;
886
887 /* If we don't have a valid negative rate, use unknown value */
888 if (Rate >= 0) Rate = BATTERY_UNKNOWN_RATE;
889
890 /* Grab the remaining capacity */
891 RemainingCapacity = FdoExtension->RemainingCapacity;
892
893 /* See if we don't know one or the other */
894 if ((Rate == BATTERY_UNKNOWN_RATE) ||
895 (RemainingCapacity == BATTERY_UNKNOWN_CAPACITY))
896 {
897 /* If the battery is discharging, we can't give out a time */
898 if ((FdoExtension->BstData.State & ACPI_BATT_STAT_DISCHARG) &&
899 (CmBattDebug & CMBATT_GENERIC_WARNING))
900 DbgPrint("CmBattQueryInformation: Can't calculate EstimatedTime.\n");
901
902 /* Check if we don't have a rate and capacity is going down */
903 if ((FdoExtension->Rate == BATTERY_UNKNOWN_RATE) &&
904 (FdoExtension->BstData.State & ACPI_BATT_STAT_DISCHARG))
905 {
906 /* We have to fail, since we lack data */
907 Status = STATUS_INVALID_DEVICE_REQUEST;
908 if (CmBattDebug & CMBATT_GENERIC_WARNING)
909 DbgPrint("---------------------- PresentRate = BATTERY_UNKNOWN_RATE\n");
910 }
911
912 /* If we don't have capacity, the rate is useless */
913 if (RemainingCapacity == BATTERY_UNKNOWN_CAPACITY)
914 {
915 /* We have to fail the request */
916 Status = STATUS_INVALID_DEVICE_REQUEST;
917 if (CmBattDebug & CMBATT_GENERIC_WARNING)
918 DbgPrint("---------------------- RemainingCapacity = BATTERY_UNKNOWN_CAPACITY\n");
919 }
920 }
921 else
922 {
923 /* We have data, but is it valid? */
924 if (RemainingCapacity > 0x123456)
925 {
926 /* The capacity seems bogus, so don't use it */
927 if (CmBattDebug & CMBATT_ACPI_WARNING)
928 DbgPrint("CmBattQueryInformation: Data Overflow in calculating Remaining Capacity.\n");
929 }
930 else
931 {
932 /* Compute the remaining time in seconds, based on rate */
933 RemainingTime = (RemainingCapacity * 3600) / -Rate;
934 }
935 }
936 }
937
938 /* Return the remaining time */
939 QueryData = &RemainingTime;
940 QueryLength = sizeof(ULONG);
941 break;
942
943 case BatteryDeviceName:
944
945 /* Build the model number string */
946 RtlInitAnsiString(&TempString, FdoExtension->ModelNumber);
947
948 /* Convert it to Unicode */
949 InfoString.Buffer = InfoBuffer;
950 InfoString.MaximumLength = sizeof(InfoBuffer);
951 Status = RtlAnsiStringToUnicodeString(&InfoString, &TempString, 0);
952
953 /* Return the unicode buffer */
954 QueryData = InfoString.Buffer;
955 QueryLength = InfoString.Length;
956 break;
957
958 case BatteryTemperature:
959 case BatteryManufactureDate:
960
961 /* We don't support these */
962 Status = STATUS_INVALID_DEVICE_REQUEST;
963 break;
964
965 case BatteryManufactureName:
966
967 /* Build the OEM info string */
968 RtlInitAnsiString(&TempString, FdoExtension->OemInfo);
969
970 /* Convert it to Unicode */
971 InfoString.Buffer = InfoBuffer;
972 InfoString.MaximumLength = sizeof(InfoBuffer);
973 Status = RtlAnsiStringToUnicodeString(&InfoString, &TempString, 0);
974
975 /* Return the unicode buffer */
976 QueryData = InfoString.Buffer;
977 QueryLength = InfoString.Length;
978 break;
979
980 case BatteryUniqueID:
981
982 /* Build the serial number string */
983 RtlInitAnsiString(&TempString, FdoExtension->SerialNumber);
984
985 /* Convert it to Unicode */
986 InfoString.Buffer = InfoBuffer;
987 InfoString.MaximumLength = sizeof(InfoBuffer);
988 RtlAnsiStringToUnicodeString(&InfoString, &TempString, 0);
989
990 /* Setup a temporary string for concatenation */
991 TempString2.Buffer = TempBuffer;
992 TempString2.MaximumLength = sizeof(TempBuffer);
993
994 /* Check if there's an OEM string */
995 if (FdoExtension->OemInfo[0])
996 {
997 /* Build the OEM info string */
998 RtlInitAnsiString(&TempString, FdoExtension->OemInfo);
999
1000 /* Convert it to Unicode and append it */
1001 RtlAnsiStringToUnicodeString(&TempString2, &TempString, 0);
1002 RtlAppendUnicodeStringToString(&InfoString, &TempString2);
1003 }
1004
1005 /* Build the model number string */
1006 RtlInitAnsiString(&TempString, FdoExtension->ModelNumber);
1007
1008 /* Convert it to Unicode and append it */
1009 RtlAnsiStringToUnicodeString(&TempString2, &TempString, 0);
1010 RtlAppendUnicodeStringToString(&InfoString, &TempString2);
1011
1012 /* Return the final appended string */
1013 QueryData = InfoString.Buffer;
1014 QueryLength = InfoString.Length;
1015 break;
1016
1017 default:
1018
1019 /* Everything else is unknown */
1020 Status = STATUS_INVALID_PARAMETER;
1021 break;
1022 }
1023
1024 /* Return the required length and check if the caller supplied enough */
1025 *ReturnedLength = QueryLength;
1026 if (BufferLength < QueryLength) Status = STATUS_BUFFER_TOO_SMALL;
1027
1028 /* Copy the data if there's enough space and it exists */
1029 if ((NT_SUCCESS(Status)) && (QueryData)) RtlCopyMemory(Buffer, QueryData, QueryLength);
1030
1031 /* Return function result */
1032 return Status;
1033 }
1034
1035 NTSTATUS
1036 NTAPI
1037 CmBattQueryStatus(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
1038 IN ULONG Tag,
1039 IN PBATTERY_STATUS BatteryStatus)
1040 {
1041 NTSTATUS Status;
1042 PAGED_CODE();
1043 if (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_INFO))
1044 DbgPrint("CmBattQueryStatus - Tag (%d) Device %x\n", Tag, DeviceExtension->DeviceId);
1045
1046 /* Query ACPI information */
1047 Status = CmBattGetBatteryStatus(DeviceExtension, Tag);
1048 if (NT_SUCCESS(Status))
1049 {
1050 BatteryStatus->PowerState = DeviceExtension->State;
1051 BatteryStatus->Capacity = DeviceExtension->RemainingCapacity;
1052 BatteryStatus->Voltage = DeviceExtension->PresentVoltage;
1053 BatteryStatus->Rate = DeviceExtension->Rate;
1054 }
1055
1056 /* Return status */
1057 if (CmBattDebug & (CMBATT_GENERIC_INFO))
1058 DbgPrint("CmBattQueryStatus: Returning [%#08lx][%#08lx][%#08lx][%#08lx]\n",
1059 BatteryStatus->PowerState,
1060 BatteryStatus->Capacity,
1061 BatteryStatus->Voltage,
1062 BatteryStatus->Rate);
1063 return Status;
1064 }
1065
1066 NTSTATUS
1067 NTAPI
1068 DriverEntry(IN PDRIVER_OBJECT DriverObject,
1069 IN PUNICODE_STRING RegistryPath)
1070 {
1071 NTSTATUS Status;
1072 PDRIVER_EXTENSION DriverExtension;
1073 OBJECT_ATTRIBUTES ObjectAttributes;
1074 UNICODE_STRING CallbackName;
1075
1076 /* Allocate registry path */
1077 GlobalRegistryPath.MaximumLength = RegistryPath->Length + sizeof(UNICODE_NULL);
1078 GlobalRegistryPath.Length = RegistryPath->Length;
1079 GlobalRegistryPath.Buffer = ExAllocatePoolWithTag(PagedPool,
1080 GlobalRegistryPath.MaximumLength,
1081 'MtaB');
1082 if (!GlobalRegistryPath.Buffer)
1083 {
1084 /* Fail if we're out of memory this early */
1085 if (CmBattDebug & CMBATT_GENERIC_WARNING)
1086 DbgPrint("CmBatt: Couldn't allocate pool for registry path.");
1087 return STATUS_INSUFFICIENT_RESOURCES;
1088 }
1089
1090 /* Buffer allocated, copy the string */
1091 RtlCopyUnicodeString(&GlobalRegistryPath, RegistryPath);
1092 if (CmBattDebug & CMBATT_GENERIC_INFO)
1093 DbgPrint("CmBatt DriverEntry - Obj (%08x) Path \"%ws\"\n",
1094 DriverObject,
1095 RegistryPath->Buffer);
1096
1097 /* Setup the major dispatchers */
1098 DriverObject->MajorFunction[IRP_MJ_CREATE] = CmBattOpenClose;
1099 DriverObject->MajorFunction[IRP_MJ_CLOSE] = CmBattOpenClose;
1100 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = CmBattIoctl;
1101 DriverObject->MajorFunction[IRP_MJ_POWER] = CmBattPowerDispatch;
1102 DriverObject->MajorFunction[IRP_MJ_PNP] = CmBattPnpDispatch;
1103 DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = CmBattSystemControl;
1104
1105 /* And the unload routine */
1106 DriverObject->DriverUnload = CmBattUnload;
1107
1108 /* And the add device routine */
1109 DriverExtension = DriverObject->DriverExtension;
1110 DriverExtension->AddDevice = CmBattAddDevice;
1111
1112 /* Create a power callback */
1113 RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState");
1114 InitializeObjectAttributes(&ObjectAttributes,
1115 &CallbackName,
1116 OBJ_KERNEL_HANDLE,
1117 NULL,
1118 NULL);
1119 Status = ExCreateCallback(&CmBattPowerCallBackObject, &ObjectAttributes, 0, TRUE);
1120 if (!NT_SUCCESS(Status))
1121 {
1122 /* No callback, fail */
1123 CmBattPowerCallBackObject = 0;
1124 if (CmBattDebug & CMBATT_GENERIC_WARNING)
1125 DbgPrint("CmBattRegisterPowerCallBack: failed status=0x%08x\n", Status);
1126 }
1127 else
1128 {
1129 /* Register the power callback now */
1130 CmBattPowerCallBackRegistration = ExRegisterCallback(CmBattPowerCallBackObject,
1131 (PVOID)CmBattPowerCallBack,
1132 DriverObject);
1133 if (CmBattPowerCallBackRegistration)
1134 {
1135 /* Last thing: setup our DPC and timer for battery wake */
1136 KeInitializeDpc(&CmBattWakeDpcObject, (PVOID)CmBattWakeDpc, DriverObject);
1137 KeInitializeTimer(&CmBattWakeDpcTimerObject);
1138 }
1139 else
1140 {
1141 ObfDereferenceObject(CmBattPowerCallBackObject);
1142 if (CmBattDebug & CMBATT_GENERIC_WARNING)
1143 DbgPrint("CmBattRegisterPowerCallBack: ExRegisterCallback failed.\n");
1144 }
1145
1146 /* All good */
1147 Status = STATUS_SUCCESS;
1148 }
1149
1150 /* Return failure or success */
1151 return Status;
1152 }
1153
1154 /* EOF */