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