• 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 #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