1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "pixelstats: BatteryEEPROM"
18 #define BATTERY_CYCLE_COUNT_PATH "/sys/class/power_supply/battery/cycle_count"
19
20 #include <log/log.h>
21 #include <time.h>
22 #include <utils/Timers.h>
23 #include <cinttypes>
24 #include <cmath>
25
26 #include <android-base/file.h>
27 #include <android-base/parseint.h>
28 #include <android-base/strings.h>
29 #include <pixelstats/BatteryEEPROMReporter.h>
30 #include <pixelstats/StatsHelper.h>
31 #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
32
33 namespace android {
34 namespace hardware {
35 namespace google {
36 namespace pixel {
37
38 using aidl::android::frameworks::stats::VendorAtom;
39 using aidl::android::frameworks::stats::VendorAtomValue;
40 using android::base::ReadFileToString;
41 using android::hardware::google::pixel::PixelAtoms::BatteryEEPROM;
42
43 #define LINESIZE 31
44 #define LINESIZE_MAX17201_HIST 80
45
BatteryEEPROMReporter()46 BatteryEEPROMReporter::BatteryEEPROMReporter() {}
47
ReadFileToInt(const std::string & path,int32_t * val)48 bool BatteryEEPROMReporter::ReadFileToInt(const std::string &path, int32_t *val) {
49 std::string file_contents;
50
51 if (!ReadFileToString(path.c_str(), &file_contents)) {
52 ALOGI("Unable to read %s - %s", path.c_str(), strerror(errno));
53 return false;
54 }
55
56 file_contents = android::base::Trim(file_contents);
57 if (!android::base::ParseInt(file_contents, val)) {
58 ALOGI("Unable to convert %s to int - %s", path.c_str(), strerror(errno));
59 return false;
60 }
61
62 return true;
63 }
64
checkPaths(const std::vector<std::string> & paths)65 std::string BatteryEEPROMReporter::checkPaths(const std::vector<std::string>& paths) {
66 if (paths.empty()) {
67 return ""; // Or throw an exception if appropriate
68 }
69
70 for (const auto& path : paths) { // Use range-based for loop
71 if (fileExists(path)) {
72 return path;
73 }
74 }
75
76 return ""; // No path found
77 }
78
checkAndReport(const std::shared_ptr<IStats> & stats_client,const std::string & path)79 void BatteryEEPROMReporter::checkAndReport(const std::shared_ptr<IStats> &stats_client,
80 const std::string &path) {
81 std::string file_contents;
82 std::string history_each;
83 std::string cycle_count;
84
85 const std::string cycle_count_path(BATTERY_CYCLE_COUNT_PATH);
86 int sparse_index_count = 0;
87
88 const int kSecondsPerMonth = 60 * 60 * 24 * 30;
89 int64_t now = getTimeSecs();
90
91 if ((report_time_ != 0) && (now - report_time_ < kSecondsPerMonth)) {
92 ALOGD("Not upload time. now: %" PRId64 ", pre: %" PRId64, now, report_time_);
93 return;
94 }
95
96 if (!ReadFileToString(path.c_str(), &file_contents)) {
97 ALOGE("Unable to read %s - %s", path.c_str(), strerror(errno));
98 return;
99 }
100
101 const int kHistTotalLen = file_contents.size();
102 const int kHistTotalNum = kHistTotalLen / LINESIZE;
103 ALOGD("kHistTotalLen=%d, kHistTotalNum=%d\n", kHistTotalLen, kHistTotalNum);
104
105 /* TODO: wait for pa/2875004 merge
106 if (ReadFileToString(cycle_count_path.c_str(), &cycle_count)) {
107 int cnt;
108
109 cycle_count = android::base::Trim(cycle_count);
110 if (android::base::ParseInt(cycle_count, &cnt)) {
111 cnt /= 10;
112 if (cnt > kHistTotalNum)
113 sparse_index_count = cnt % kHistTotalNum;
114 }
115
116 ALOGD("sparse_index_count %d cnt: %d cycle_count %s\n", sparse_index_count, cnt,
117 cycle_count.c_str());
118 }
119 */
120
121 struct BatteryEEPROMPipelineRawFormat hist_raw;
122 struct BatteryEEPROMPipeline hist;
123 int16_t i;
124
125 ReadFileToInt(kBatteryPairingPath, &hist.battery_pairing);
126
127 for (i = 0; i < kHistTotalNum; i++) {
128 size_t history_offset = i * LINESIZE;
129 if (history_offset + LINESIZE > kHistTotalLen)
130 break;
131 history_each = file_contents.substr(history_offset, LINESIZE);
132 unsigned int data[4];
133
134 /* Format transfer: go/gsx01-eeprom */
135 int16_t num = sscanf(history_each.c_str(), "%4" SCNx16 "%4" SCNx16 "%x %x %x %x",
136 &hist_raw.tempco, &hist_raw.rcomp0, &data[0], &data[1], &data[2], &data[3]);
137 if (num <= 0)
138 continue;
139
140 if (hist_raw.tempco == 0xFFFF && hist_raw.rcomp0 == 0xFFFF)
141 continue;
142
143 /* Extract each data */
144 uint64_t tmp = (int64_t)data[3] << 48 |
145 (int64_t)data[2] << 32 |
146 (int64_t)data[1] << 16 |
147 data[0];
148
149 /* ignore this data if unreasonable */
150 if (tmp <= 0)
151 continue;
152
153 /* data format/unit in go/gsx01-eeprom#heading=h.finy98ign34p */
154 hist_raw.timer_h = tmp & 0xFF;
155 hist_raw.fullcapnom = (tmp >>= 8) & 0x3FF;
156 hist_raw.fullcaprep = (tmp >>= 10) & 0x3FF;
157 hist_raw.mixsoc = (tmp >>= 10) & 0x3F;
158 hist_raw.vfsoc = (tmp >>= 6) & 0x3F;
159 hist_raw.maxvolt = (tmp >>= 6) & 0xF;
160 hist_raw.minvolt = (tmp >>= 4) & 0xF;
161 hist_raw.maxtemp = (tmp >>= 4) & 0xF;
162 hist_raw.mintemp = (tmp >>= 4) & 0xF;
163 hist_raw.maxchgcurr = (tmp >>= 4) & 0xF;
164 hist_raw.maxdischgcurr = (tmp >>= 4) & 0xF;
165
166 /* Mapping to original format to collect data */
167 /* go/pixel-battery-eeprom-atom#heading=h.dcawdjiz2ls6 */
168 hist.tempco = (int32_t)hist_raw.tempco;
169 hist.rcomp0 = (int32_t)hist_raw.rcomp0;
170 hist.timer_h = (int32_t)hist_raw.timer_h * 5;
171 hist.max_temp = (int32_t)hist_raw.maxtemp * 3 + 22;
172 hist.min_temp = (int32_t)hist_raw.mintemp * 3 - 20;
173 hist.min_ibatt = (int32_t)hist_raw.maxchgcurr * 500 * (-1);
174 hist.max_ibatt = (int32_t)hist_raw.maxdischgcurr * 500;
175 hist.min_vbatt = (int32_t)hist_raw.minvolt * 10 + 2500;
176 hist.max_vbatt = (int32_t)hist_raw.maxvolt * 20 + 4200;
177 hist.batt_soc = (int32_t)hist_raw.vfsoc * 2;
178 hist.msoc = (int32_t)hist_raw.mixsoc * 2;
179 hist.full_cap = (int32_t)hist_raw.fullcaprep * 125 / 1000;
180 hist.full_rep = (int32_t)hist_raw.fullcapnom * 125 / 1000;
181
182 /* i < sparse_index_count: 20 40 60 80 */
183 if (i < sparse_index_count)
184 hist.cycle_cnt = (i + 1) * 20;
185 else
186 hist.cycle_cnt = (i + sparse_index_count + 1) * 10;
187
188 reportEvent(stats_client, hist);
189 report_time_ = getTimeSecs();
190 }
191 return;
192 }
193
getTimeSecs(void)194 int64_t BatteryEEPROMReporter::getTimeSecs(void) {
195 return nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
196 }
197
reportEvent(const std::shared_ptr<IStats> & stats_client,const struct BatteryEEPROMPipeline & hist)198 void BatteryEEPROMReporter::reportEvent(const std::shared_ptr<IStats> &stats_client,
199 const struct BatteryEEPROMPipeline &hist) {
200 std::vector<VendorAtomValue> values(kNumEEPROMPipelineFields);
201
202 ALOGD("reportEvent: cycle_cnt:%d, full_cap:%d, esr:%d, rslow:%d, soh:%d, "
203 "batt_temp:%d, cutoff_soc:%d, cc_soc:%d, sys_soc:%d, msoc:%d, "
204 "batt_soc:%d, reserve:%d, max_temp:%d, min_temp:%d, max_vbatt:%d, "
205 "min_vbatt:%d, max_ibatt:%d, min_ibatt:%d, checksum:%#x, full_rep:%d, "
206 "tempco:%#x, rcomp0:%#x, timer_h:%d, batt_pair:%d",
207 hist.cycle_cnt, hist.full_cap, hist.esr, hist.rslow, hist.soh, hist.batt_temp,
208 hist.cutoff_soc, hist.cc_soc, hist.sys_soc, hist.msoc, hist.batt_soc, hist.reserve,
209 hist.max_temp, hist.min_temp, hist.max_vbatt, hist.min_vbatt, hist.max_ibatt,
210 hist.min_ibatt, hist.checksum, hist.full_rep, hist.tempco, hist.rcomp0, hist.timer_h,
211 hist.battery_pairing);
212
213 setAtomFieldValue(&values, BatteryEEPROM::kCycleCntFieldNumber, hist.cycle_cnt);
214 setAtomFieldValue(&values, BatteryEEPROM::kFullCapFieldNumber, hist.full_cap);
215 setAtomFieldValue(&values, BatteryEEPROM::kEsrFieldNumber, hist.esr);
216 setAtomFieldValue(&values, BatteryEEPROM::kRslowFieldNumber, hist.rslow);
217 setAtomFieldValue(&values, BatteryEEPROM::kSohFieldNumber, hist.soh);
218 setAtomFieldValue(&values, BatteryEEPROM::kBattTempFieldNumber, hist.batt_temp);
219 setAtomFieldValue(&values, BatteryEEPROM::kCutoffSocFieldNumber, hist.cutoff_soc);
220 setAtomFieldValue(&values, BatteryEEPROM::kCcSocFieldNumber, hist.cc_soc);
221 setAtomFieldValue(&values, BatteryEEPROM::kSysSocFieldNumber, hist.sys_soc);
222 setAtomFieldValue(&values, BatteryEEPROM::kMsocFieldNumber, hist.msoc);
223 setAtomFieldValue(&values, BatteryEEPROM::kBattSocFieldNumber, hist.batt_soc);
224 setAtomFieldValue(&values, BatteryEEPROM::kReserveFieldNumber, hist.reserve);
225 setAtomFieldValue(&values, BatteryEEPROM::kMaxTempFieldNumber, hist.max_temp);
226 setAtomFieldValue(&values, BatteryEEPROM::kMinTempFieldNumber, hist.min_temp);
227 setAtomFieldValue(&values, BatteryEEPROM::kMaxVbattFieldNumber, hist.max_vbatt);
228 setAtomFieldValue(&values, BatteryEEPROM::kMinVbattFieldNumber, hist.min_vbatt);
229 setAtomFieldValue(&values, BatteryEEPROM::kMaxIbattFieldNumber, hist.max_ibatt);
230 setAtomFieldValue(&values, BatteryEEPROM::kMinIbattFieldNumber, hist.min_ibatt);
231 setAtomFieldValue(&values, BatteryEEPROM::kChecksumFieldNumber, hist.checksum);
232 setAtomFieldValue(&values, BatteryEEPROM::kTempcoFieldNumber, hist.tempco);
233 setAtomFieldValue(&values, BatteryEEPROM::kRcomp0FieldNumber, hist.rcomp0);
234 setAtomFieldValue(&values, BatteryEEPROM::kTimerHFieldNumber, hist.timer_h);
235 setAtomFieldValue(&values, BatteryEEPROM::kFullRepFieldNumber, hist.full_rep);
236 setAtomFieldValue(&values, BatteryEEPROM::kBatteryPairingFieldNumber, hist.battery_pairing);
237
238 VendorAtom event = {.reverseDomainName = "",
239 .atomId = PixelAtoms::Atom::kBatteryEeprom,
240 .values = std::move(values)};
241 reportVendorAtom(stats_client, event);
242 }
243
checkAndReportGMSR(const std::shared_ptr<IStats> & stats_client,const std::vector<std::string> & paths)244 void BatteryEEPROMReporter::checkAndReportGMSR(const std::shared_ptr<IStats> &stats_client,
245 const std::vector<std::string> &paths) {
246 struct BatteryEEPROMPipeline gmsr = {.checksum = EvtGMSR};
247 std::string path = checkPaths(paths);
248 std::string file_contents;
249 int16_t num;
250
251 if (path.empty())
252 return;
253
254 if (!ReadFileToString(path, &file_contents)) {
255 ALOGE("Unable to read gmsr path: %s - %s", path.c_str(), strerror(errno));
256 return;
257 }
258
259 num = sscanf(file_contents.c_str(), "rcomp0\t:%x\ntempco\t:%x\nfullcaprep\t:%x\ncycles\t:%x"
260 "\nfullcapnom\t:%x\nqresidual00\t:%x\nqresidual10\t:%x\nqresidual20\t:%x"
261 "\nqresidual30\t:%x\ncv_mixcap\t:%x\nhalftime\t:%x",
262 &gmsr.rcomp0, &gmsr.tempco, &gmsr.full_rep, &gmsr.cycle_cnt, &gmsr.full_cap,
263 &gmsr.max_vbatt, &gmsr.min_vbatt, &gmsr.max_ibatt, &gmsr.min_ibatt,
264 &gmsr.esr, &gmsr.rslow);
265 if (num != kNum77759GMSRFields && num != kNum77779GMSRFields) {
266 ALOGE("Couldn't process GMSR. num=%d\n", num);
267 return;
268 }
269
270 if (gmsr.tempco == 0xFFFF || gmsr.rcomp0 == 0xFFFF || gmsr.full_cap == 0xFFFF) {
271 ALOGD("Ignore invalid gmsr");
272 return;
273 }
274
275 reportEvent(stats_client, gmsr);
276 }
277
checkAndReportMaxfgHistory(const std::shared_ptr<IStats> & stats_client,const std::string & path)278 void BatteryEEPROMReporter::checkAndReportMaxfgHistory(const std::shared_ptr<IStats> &stats_client,
279 const std::string &path) {
280 std::string file_contents;
281 int16_t i;
282
283 if (path.empty())
284 return;
285
286 /* not support max17201 */
287 if (!ReadFileToString(path, &file_contents))
288 return;
289
290 std::string hist_each;
291 const int kHistTotalLen = file_contents.size();
292
293 ALOGD("checkAndReportMaxfgHistory:size=%d\n%s", kHistTotalLen, file_contents.c_str());
294
295 for (i = 0; i < kHistTotalLen; i++) {
296 struct BatteryEEPROMPipeline maxfg_hist;
297 uint16_t nQRTable00, nQRTable10, nQRTable20, nQRTable30, nCycles, nFullCapNom;
298 uint16_t nRComp0, nTempCo, nIAvgEmpty, nFullCapRep, nVoltTemp, nMaxMinCurr, nMaxMinVolt;
299 uint16_t nMaxMinTemp, nSOC, nTimerH;
300 int16_t num;
301 size_t hist_offset = i * LINESIZE_MAX17201_HIST;
302
303 if (hist_offset >= file_contents.size())
304 break;
305
306 hist_each = file_contents.substr(hist_offset, LINESIZE_MAX17201_HIST);
307 num = sscanf(hist_each.c_str(), "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16
308 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16
309 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16,
310 &nQRTable00, &nQRTable10, &nQRTable20, &nQRTable30, &nCycles, &nFullCapNom,
311 &nRComp0, &nTempCo, &nIAvgEmpty, &nFullCapRep, &nVoltTemp, &nMaxMinCurr,
312 &nMaxMinVolt, &nMaxMinTemp, &nSOC, &nTimerH);
313
314 if (num != kNum17201HISTFields) {
315 ALOGE("Couldn't process %s (num=%d)", hist_each.c_str(), num);
316 continue;
317 }
318
319 /* not assign: nQRTable00, nQRTable10, nQRTable20, nQRTable30 */
320 maxfg_hist.reserve = 0xFF;
321 maxfg_hist.tempco = nTempCo;
322 maxfg_hist.rcomp0 = nRComp0;
323 maxfg_hist.full_rep = nFullCapNom;
324 maxfg_hist.full_cap = nFullCapRep;
325 maxfg_hist.cycle_cnt = nCycles * 16 / 100; // LSB: 16%;
326 maxfg_hist.timer_h = (nTimerH * 32 / 10) / 24; // LSB: 3.2 hours
327 maxfg_hist.batt_soc = (nSOC >> 8) & 0x00FF;
328 maxfg_hist.msoc = nSOC & 0x00FF;
329 maxfg_hist.max_ibatt = ((nMaxMinCurr >> 8) & 0x00FF) * 80;
330 maxfg_hist.min_ibatt = (nMaxMinCurr & 0x00FF) * 80 * (-1);
331 maxfg_hist.max_vbatt = ((nMaxMinVolt >> 8) & 0x00FF) * 20;
332 maxfg_hist.min_vbatt = (nMaxMinVolt & 0x00FF) * 20;
333 maxfg_hist.max_temp = (nMaxMinTemp >> 8) & 0x00FF;
334 maxfg_hist.min_temp = nMaxMinTemp & 0x00FF;
335 maxfg_hist.esr = nIAvgEmpty;
336 maxfg_hist.rslow = nVoltTemp;
337
338 reportEvent(stats_client, maxfg_hist);
339 }
340 }
341
checkAndReportFGModelLoading(const std::shared_ptr<IStats> & client,const std::vector<std::string> & paths)342 void BatteryEEPROMReporter::checkAndReportFGModelLoading(const std::shared_ptr<IStats> &client,
343 const std::vector<std::string> &paths) {
344 struct BatteryEEPROMPipeline params = {.full_cap = 0, .esr = 0, .rslow = 0,
345 .checksum = EvtModelLoading, };
346 std::string path = checkPaths(paths);
347 std::string file_contents;
348 int num;
349 const char *data;
350
351 if (path.empty())
352 return;
353
354 /* not found */
355 if (path.empty())
356 return;
357
358 if (!ReadFileToString(path, &file_contents)) {
359 ALOGE("Unable to read ModelLoading History path: %s - %s", path.c_str(), strerror(errno));
360 return;
361 }
362
363 data = file_contents.c_str();
364
365 num = sscanf(data, "ModelNextUpdate: %x%*[0-9a-f: \n]ATT: %x FAIL: %x",
366 ¶ms.rslow, ¶ms.full_cap, ¶ms.esr);
367 if (num != 3) {
368 ALOGE("Couldn't process ModelLoading History. num=%d\n", num);
369 return;
370 }
371
372 /* don't need to report when attempts counter is zero */
373 if (params.full_cap == 0)
374 return;
375
376 reportEvent(client, params);
377 }
378
checkAndReportFGLearning(const std::shared_ptr<IStats> & stats_client,const std::vector<std::string> & paths)379 void BatteryEEPROMReporter::checkAndReportFGLearning(const std::shared_ptr<IStats> &stats_client,
380 const std::vector<std::string> &paths) {
381 struct BatteryEEPROMPipeline params = {.checksum = EvtFGLearningHistory};
382 std::string path = checkPaths(paths);
383 struct timespec boot_time;
384 auto format = FormatIgnoreAddr;
385 std::vector<std::vector<uint32_t>> events;
386
387 if (path.empty())
388 return;
389
390 clock_gettime(CLOCK_MONOTONIC, &boot_time);
391
392 readLogbuffer(path, kNumFGLearningFieldsV3, params.checksum, format, last_lh_check_, events);
393 if (events.size() == 0)
394 readLogbuffer(path, kNumFGLearningFieldsV2, params.checksum, format, last_lh_check_, events);
395
396 for (int event_idx = 0; event_idx < events.size(); event_idx++) {
397 std::vector<uint32_t> &event = events[event_idx];
398 if (event.size() == kNumFGLearningFieldsV2 ||
399 event.size() == kNumFGLearningFieldsV3) {
400 params.full_cap = event[0]; /* fcnom */
401 params.esr = event[1]; /* dpacc */
402 params.rslow = event[2]; /* dqacc */
403 params.full_rep = event[3]; /* fcrep */
404 params.msoc = event[4] >> 8; /* repsoc */
405 params.sys_soc = event[5] >> 8; /* mixsoc */
406 params.batt_soc = event[6] >> 8; /* vfsoc */
407 params.min_ibatt = event[7]; /* fstats */
408 params.max_temp = event[8] >> 8; /* avgtemp */
409 params.min_temp = event[9] >> 8; /* temp */
410 params.max_ibatt = event[10]; /* qh */
411 params.max_vbatt = event[11]; /* vcell */
412 params.min_vbatt = event[12]; /* avgvcell */
413 params.cycle_cnt = event[13]; /* vfocf */
414 params.rcomp0 = event[14]; /* rcomp0 */
415 params.tempco = event[15]; /* tempco */
416 if (event.size() == kNumFGLearningFieldsV3)
417 params.soh = event[16]; /* unix time */
418 } else {
419 ALOGE("Not support %zu fields for FG learning event", event.size());
420 continue;
421 }
422 reportEvent(stats_client, params);
423 }
424 last_lh_check_ = (unsigned int)boot_time.tv_sec;
425 }
426
checkAndReportValidation(const std::shared_ptr<IStats> & stats_client,const std::vector<std::string> & paths)427 void BatteryEEPROMReporter::checkAndReportValidation(const std::shared_ptr<IStats> &stats_client,
428 const std::vector<std::string> &paths) {
429 struct BatteryEEPROMPipeline params = {.checksum = EvtHistoryValidation};
430 std::string path = checkPaths(paths);
431 struct timespec boot_time;
432 auto format = FormatIgnoreAddr;
433 std::vector<std::vector<uint32_t>> events;
434
435 if (path.empty())
436 return;
437
438 clock_gettime(CLOCK_MONOTONIC, &boot_time);
439
440 readLogbuffer(path, kNumValidationFields, params.checksum, format, last_hv_check_, events);
441 for (int event_idx = 0; event_idx < events.size(); event_idx++) {
442 std::vector<uint32_t> &event = events[event_idx];
443 if (event.size() == kNumValidationFields) {
444 params.full_cap = event[0]; /* first empty entry */
445 params.esr = event[1]; /* num of entries need to be recovered or fix result */
446 params.rslow = event[2]; /* last cycle count */
447 params.full_rep = event[3]; /* estimate cycle count after recovery */
448 reportEvent(stats_client, params);
449 /* force report history metrics if it was recovered */
450 if (last_hv_check_ != 0) {
451 report_time_ = 0;
452 }
453 } else {
454 ALOGE("Not support %zu fields for History Validation event", event.size());
455 }
456 }
457 last_hv_check_ = (unsigned int)boot_time.tv_sec;
458 }
459
460 } // namespace pixel
461 } // namespace google
462 } // namespace hardware
463 } // namespace android
464