• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/power_monitor/battery_level_provider.h"
6 
7 #define INITGUID
8 #include <windows.h>  // Must be in front of other Windows header files.
9 
10 #include <devguid.h>
11 #include <poclass.h>
12 #include <setupapi.h>
13 #include <winioctl.h>
14 
15 #include <algorithm>
16 #include <array>
17 #include <vector>
18 
19 #include "base/memory/weak_ptr.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/numerics/safe_conversions.h"
22 #include "base/task/sequenced_task_runner.h"
23 #include "base/task/task_traits.h"
24 #include "base/task/thread_pool.h"
25 #include "base/threading/scoped_blocking_call.h"
26 #include "base/win/scoped_devinfo.h"
27 #include "base/win/scoped_handle.h"
28 
29 namespace base {
30 namespace {
31 
32 // Returns a handle to the battery interface identified by |interface_data|, or
33 // nullopt if the request failed. |devices| is a device information set that
34 // contains battery devices information, obtained with ::SetupDiGetClassDevs().
GetBatteryHandle(HDEVINFO devices,SP_DEVICE_INTERFACE_DATA * interface_data)35 base::win::ScopedHandle GetBatteryHandle(
36     HDEVINFO devices,
37     SP_DEVICE_INTERFACE_DATA* interface_data) {
38   // Query size required to hold |interface_detail|.
39   DWORD required_size = 0;
40   ::SetupDiGetDeviceInterfaceDetail(devices, interface_data, nullptr, 0,
41                                     &required_size, nullptr);
42   DWORD error = ::GetLastError();
43   if (error != ERROR_INSUFFICIENT_BUFFER)
44     return base::win::ScopedHandle();
45 
46   // |interface_detail->DevicePath| is variable size.
47   std::vector<uint8_t> raw_buf(required_size);
48   auto* interface_detail =
49       reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(raw_buf.data());
50   interface_detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
51 
52   BOOL success = ::SetupDiGetDeviceInterfaceDetail(
53       devices, interface_data, interface_detail, required_size, nullptr,
54       nullptr);
55   if (!success)
56     return base::win::ScopedHandle();
57 
58   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
59                                                 base::BlockingType::MAY_BLOCK);
60   base::win::ScopedHandle battery(
61       ::CreateFile(interface_detail->DevicePath, GENERIC_READ | GENERIC_WRITE,
62                    FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
63                    FILE_ATTRIBUTE_NORMAL, nullptr));
64   return battery;
65 }
66 
67 // Returns the current tag for `battery` handle, BATTERY_TAG_INVALID if there is
68 // no battery present in this interface or nullopt on retrieval error.
69 // See
70 // https://docs.microsoft.com/en-us/windows/win32/power/ioctl-battery-query-tag
GetBatteryTag(HANDLE battery)71 absl::optional<ULONG> GetBatteryTag(HANDLE battery) {
72   ULONG battery_tag = 0;
73   ULONG wait = 0;
74   DWORD bytes_returned = 0;
75   BOOL success = ::DeviceIoControl(
76       battery, IOCTL_BATTERY_QUERY_TAG, &wait, sizeof(wait), &battery_tag,
77       sizeof(battery_tag), &bytes_returned, nullptr);
78   if (!success) {
79     if (::GetLastError() == ERROR_FILE_NOT_FOUND) {
80       // No battery present in this interface.
81       //
82       // TODO(crbug.com/1191045): Change CHECK to DCHECK in October 2022 after
83       // verifying that there are no crash reports.
84       CHECK_EQ(battery_tag, static_cast<ULONG>(BATTERY_TAG_INVALID));
85       return battery_tag;
86     }
87     // Retrieval error.
88     return absl::nullopt;
89   }
90   return battery_tag;
91 }
92 
93 // Returns BATTERY_INFORMATION structure containing battery information, given
94 // battery handle and tag, or nullopt if the request failed. Battery handle and
95 // tag are obtained with GetBatteryHandle() and GetBatteryTag(), respectively.
GetBatteryInformation(HANDLE battery,ULONG battery_tag)96 absl::optional<BATTERY_INFORMATION> GetBatteryInformation(HANDLE battery,
97                                                           ULONG battery_tag) {
98   BATTERY_QUERY_INFORMATION query_information = {};
99   query_information.BatteryTag = battery_tag;
100   query_information.InformationLevel = BatteryInformation;
101   BATTERY_INFORMATION battery_information = {};
102   DWORD bytes_returned;
103   BOOL success = ::DeviceIoControl(
104       battery, IOCTL_BATTERY_QUERY_INFORMATION, &query_information,
105       sizeof(query_information), &battery_information,
106       sizeof(battery_information), &bytes_returned, nullptr);
107   if (!success)
108     return absl::nullopt;
109   return battery_information;
110 }
111 
112 // Returns the granularity of the battery discharge.
GetBatteryBatteryDischargeGranularity(HANDLE battery,ULONG battery_tag,ULONG current_capacity,ULONG designed_capacity)113 absl::optional<uint32_t> GetBatteryBatteryDischargeGranularity(
114     HANDLE battery,
115     ULONG battery_tag,
116     ULONG current_capacity,
117     ULONG designed_capacity) {
118   BATTERY_QUERY_INFORMATION query_information = {};
119   query_information.BatteryTag = battery_tag;
120   query_information.InformationLevel = BatteryGranularityInformation;
121 
122   // The battery discharge granularity can change as the level of the battery
123   // gets closer to zero. The documentation for `BatteryGranularityInformation`
124   // says that a maximum of 4 scales is possible. Each scale contains the
125   // granularity (in mWh) and the capacity (in mWh) at which the scale takes
126   // effect.
127   // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-battery_reporting_scale
128   std::array<BATTERY_REPORTING_SCALE, 4> battery_reporting_scales;
129 
130   DWORD bytes_returned = 0;
131   BOOL success = ::DeviceIoControl(
132       battery, IOCTL_BATTERY_QUERY_INFORMATION, &query_information,
133       sizeof(query_information), &battery_reporting_scales,
134       sizeof(battery_reporting_scales), &bytes_returned, nullptr);
135   if (!success)
136     return absl::nullopt;
137 
138   ptrdiff_t nb_elements = base::checked_cast<ptrdiff_t>(
139       bytes_returned / sizeof(BATTERY_REPORTING_SCALE));
140   if (!nb_elements)
141     return absl::nullopt;
142 
143   // The granularities are ordered from the highest capacity to the lowest
144   // capacity, or from the most coarse granularity to the most precise
145   // granularity, according to the documentation.
146   // Just in case, the documentation is not trusted for |max_granularity|. All
147   // the values are still compared to find the most coarse granularity.
148   DWORD max_granularity =
149       std::max_element(std::begin(battery_reporting_scales),
150                        std::begin(battery_reporting_scales) + nb_elements,
151                        [](const auto& lhs, const auto& rhs) {
152                          return lhs.Granularity < rhs.Granularity;
153                        })
154           ->Granularity;
155 
156   // Check if the API can be trusted, which would simplify the implementation of
157   // this function.
158   UMA_HISTOGRAM_BOOLEAN(
159       "Power.BatteryDischargeGranularityIsOrdered",
160       max_granularity == battery_reporting_scales[0].Granularity);
161 
162   return max_granularity;
163 }
164 
165 // Returns BATTERY_STATUS structure containing battery state, given battery
166 // handle and tag, or nullopt if the request failed. Battery handle and tag are
167 // obtained with GetBatteryHandle() and GetBatteryTag(), respectively.
GetBatteryStatus(HANDLE battery,ULONG battery_tag)168 absl::optional<BATTERY_STATUS> GetBatteryStatus(HANDLE battery,
169                                                 ULONG battery_tag) {
170   BATTERY_WAIT_STATUS wait_status = {};
171   wait_status.BatteryTag = battery_tag;
172   BATTERY_STATUS battery_status;
173   DWORD bytes_returned;
174   BOOL success = ::DeviceIoControl(
175       battery, IOCTL_BATTERY_QUERY_STATUS, &wait_status, sizeof(wait_status),
176       &battery_status, sizeof(battery_status), &bytes_returned, nullptr);
177   if (!success)
178     return absl::nullopt;
179   return battery_status;
180 }
181 
182 }  // namespace
183 
184 class BatteryLevelProviderWin : public BatteryLevelProvider {
185  public:
186   BatteryLevelProviderWin() = default;
187   ~BatteryLevelProviderWin() override = default;
188 
GetBatteryState(base::OnceCallback<void (const absl::optional<BatteryState> &)> callback)189   void GetBatteryState(
190       base::OnceCallback<void(const absl::optional<BatteryState>&)> callback)
191       override {
192     // This is run on |blocking_task_runner_| since `GetBatteryStateImpl()` has
193     // blocking calls and can take several seconds to complete.
194     blocking_task_runner_->PostTaskAndReplyWithResult(
195         FROM_HERE,
196         base::BindOnce(&BatteryLevelProviderWin::GetBatteryStateImpl),
197         base::BindOnce(&BatteryLevelProviderWin::OnBatteryStateObtained,
198                        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
199   }
200 
201  private:
202   static absl::optional<BatteryState> GetBatteryStateImpl();
203 
OnBatteryStateObtained(base::OnceCallback<void (const absl::optional<BatteryState> &)> callback,const absl::optional<BatteryState> & battery_state)204   void OnBatteryStateObtained(
205       base::OnceCallback<void(const absl::optional<BatteryState>&)> callback,
206       const absl::optional<BatteryState>& battery_state) {
207     std::move(callback).Run(battery_state);
208   }
209 
210   // TaskRunner used to run blocking `GetBatteryStateImpl()` queries, sequenced
211   // to avoid the performance cost of concurrent calls.
212   scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_{
213       base::ThreadPool::CreateSequencedTaskRunner(
214           {base::MayBlock(),
215            base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})};
216 
217   base::WeakPtrFactory<BatteryLevelProviderWin> weak_ptr_factory_{this};
218 };
219 
Create()220 std::unique_ptr<BatteryLevelProvider> BatteryLevelProvider::Create() {
221   return std::make_unique<BatteryLevelProviderWin>();
222 }
223 
224 // static
225 absl::optional<BatteryLevelProvider::BatteryState>
GetBatteryStateImpl()226 BatteryLevelProviderWin::GetBatteryStateImpl() {
227   // Proactively mark as blocking to fail early, since calls below may also
228   // trigger ScopedBlockingCall.
229   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
230                                                 base::BlockingType::MAY_BLOCK);
231 
232   // Battery interfaces are enumerated at every sample to detect when a new
233   // interface is added, and avoid holding dangling handles when a battery is
234   // disconnected.
235   base::win::ScopedDevInfo devices(::SetupDiGetClassDevs(
236       &GUID_DEVICE_BATTERY, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
237   if (!devices.is_valid()) {
238     return absl::nullopt;
239   }
240 
241   std::vector<BatteryDetails> battery_details_list;
242 
243   // The algorithm to enumerate battery devices is taken from
244   // https://docs.microsoft.com/en-us/windows/win32/power/enumerating-battery-devices
245   // Limit search to 8 batteries max. A system may have several battery slots
246   // and each slot may hold an actual battery.
247   for (DWORD device_index = 0; device_index < 8; ++device_index) {
248     SP_DEVICE_INTERFACE_DATA interface_data = {};
249     interface_data.cbSize = sizeof(interface_data);
250 
251     BOOL success =
252         ::SetupDiEnumDeviceInterfaces(devices.get(), 0, &GUID_DEVCLASS_BATTERY,
253                                       device_index, &interface_data);
254     if (!success) {
255       // Enumeration ended normally.
256       if (::GetLastError() == ERROR_NO_MORE_ITEMS)
257         break;
258       // Error.
259       return absl::nullopt;
260     }
261 
262     base::win::ScopedHandle battery =
263         GetBatteryHandle(devices.get(), &interface_data);
264     if (!battery.IsValid())
265       return absl::nullopt;
266 
267     absl::optional<ULONG> battery_tag = GetBatteryTag(battery.Get());
268     if (!battery_tag.has_value()) {
269       return absl::nullopt;
270     } else if (battery_tag.value() == BATTERY_TAG_INVALID) {
271       // No battery present in this interface.
272       continue;
273     }
274 
275     auto battery_information =
276         GetBatteryInformation(battery.Get(), *battery_tag);
277     if (!battery_information.has_value()) {
278       return absl::nullopt;
279     }
280 
281     auto battery_status = GetBatteryStatus(battery.Get(), *battery_tag);
282     if (!battery_status.has_value()) {
283       return absl::nullopt;
284     }
285 
286     absl::optional<uint32_t> battery_discharge_granularity =
287         GetBatteryBatteryDischargeGranularity(
288             battery.Get(), *battery_tag, battery_status->Capacity,
289             battery_information->DesignedCapacity);
290 
291     battery_details_list.push_back(BatteryDetails(
292         {.is_external_power_connected =
293              !!(battery_status->PowerState & BATTERY_POWER_ON_LINE),
294          .current_capacity = battery_status->Capacity,
295          .full_charged_capacity = battery_information->FullChargedCapacity,
296          .charge_unit =
297              ((battery_information->Capabilities & BATTERY_CAPACITY_RELATIVE)
298                   ? BatteryLevelUnit::kRelative
299                   : BatteryLevelUnit::kMWh),
300          .battery_discharge_granularity = battery_discharge_granularity}));
301   }
302 
303   return MakeBatteryState(battery_details_list);
304 }
305 
306 }  // namespace base
307