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