ohmu
·
2026-10-15
1#include "battery.h"
2#include "common/debug.h"
3
4#include <kstat.h>
5
6static inline void kstatFreeWrap(kstat_ctl_t** pkc) {
7 assert(pkc);
8 if (*pkc) {
9 kstat_close(*pkc);
10 }
11}
12
13static bool getUint32(kstat_t* ksp, const char* name, uint32_t* value) {
14 kstat_named_t* stat = kstat_data_lookup(ksp, name);
15 if (!stat) {
16 return false;
17 }
18
19 switch (stat->data_type) {
20 case KSTAT_DATA_UINT32:
21 *value = stat->value.ui32;
22 return true;
23 case KSTAT_DATA_INT32:
24 if (stat->value.i32 >= 0) {
25 *value = (uint32_t) stat->value.i32;
26 return true;
27 }
28 break;
29#ifdef _INT64_TYPE
30 case KSTAT_DATA_UINT64:
31 if (stat->value.ui64 <= UINT32_MAX) {
32 *value = (uint32_t) stat->value.ui64;
33 return true;
34 }
35 break;
36 case KSTAT_DATA_INT64:
37 if (stat->value.i64 >= 0 && (uint64_t) stat->value.i64 <= UINT32_MAX) {
38 *value = (uint32_t) stat->value.i64;
39 return true;
40 }
41 break;
42#endif
43 default:
44 break;
45 }
46
47 return false;
48}
49
50const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist* results) {
51 // https://github.com/illumos/illumos-gate/blob/master/usr/src/cmd/powertop/common/battery.c
52 [[gnu::cleanup(kstatFreeWrap)]] kstat_ctl_t* kc = kstat_open();
53 if (!kc) {
54 FF_DEBUG("kstat_open() failed");
55 return "kstat_open() failed";
56 }
57
58 // illumos exposes battery information through either of these modules.
59 const char* modules[] = { "battery", "acpi_drv" };
60 kstat_t* bif = nullptr;
61 kstat_t* bst = nullptr;
62 for (size_t i = 0; i < ARRAY_SIZE(modules); ++i) {
63 bif = kstat_lookup(kc, modules[i], 0, "battery BIF0");
64 bst = kstat_lookup(kc, modules[i], 0, "battery BST0");
65 if (bif && bst) {
66 break;
67 }
68 bif = nullptr;
69 bst = nullptr;
70 }
71
72 if (!bif || !bst || kstat_read(kc, bif, nullptr) < 0 || kstat_read(kc, bst, nullptr) < 0) {
73 return nullptr;
74 }
75
76 uint32_t lastCapacity, remainingCapacity, rate, state;
77 if (!getUint32(bif, "bif_last_cap", &lastCapacity) ||
78 !getUint32(bst, "bst_rem_cap", &remainingCapacity) ||
79 !getUint32(bst, "bst_state", &state)) {
80 FF_DEBUG("kstat_data_lookup() failed for bif_last_cap, bst_rem_cap or bst_state");
81 return "kstat_data_lookup() failed";
82 }
83
84 if (lastCapacity == 0 || remainingCapacity == UINT32_MAX) {
85 return nullptr;
86 }
87
88 FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
89 ffStrbufInit(&battery->manufacturer);
90 ffStrbufInit(&battery->manufactureDate);
91 ffStrbufInit(&battery->modelName);
92 ffStrbufInit(&battery->technology);
93 ffStrbufInit(&battery->serial);
94 battery->status = FF_BATTERY_STATUS_NONE;
95 battery->capacity = (double) remainingCapacity * 100.0 / (double) lastCapacity;
96 battery->temperature = FF_BATTERY_TEMP_UNSET;
97 battery->cycleCount = 0;
98 battery->timeRemaining = -1;
99
100 // illumos BST state: bit 0 discharging, bit 1 charging, bit 2 critical.
101 if (state & (1u << 0)) {
102 battery->status |= FF_BATTERY_STATUS_DISCHARGING;
103 }
104 if (state & (1u << 1)) {
105 battery->status |= FF_BATTERY_STATUS_CHARGING;
106 }
107 if (state & (1u << 2)) {
108 battery->status |= FF_BATTERY_STATUS_CRITICAL;
109 }
110
111 if ((state & (1u << 0)) && getUint32(bst, "bst_rate", &rate) && rate > 0 && rate != UINT32_MAX) {
112 battery->timeRemaining = (int32_t) (((uint64_t) remainingCapacity * 3600) / rate);
113 }
114
115 return nullptr;
116}