1 /*
2  * Copyright (C) 2017 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 package com.android.car.storagemonitoring;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.util.Slog;
21 
22 import com.android.car.CarLog;
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.Scanner;
30 import java.util.regex.MatchResult;
31 import java.util.regex.Pattern;
32 
33 /**
34  * Loads wear information from the UFS sysfs entry points.
35  * sysfs exposes UFS lifetime data in /sys/devices/soc/624000.ufshc/health
36  * The first line of the file contains the UFS version
37  * Subsequent lines contains individual information points in the format:
38  * Health Descriptor[Byte offset 0x%d]: %31s = 0x%hx
39  * Of these we care about the key values bPreEOLInfo and bDeviceLifeTimeEstA bDeviceLifeTimeEstB
40  */
41 public class UfsWearInformationProvider implements WearInformationProvider {
42     private static File DEFAULT_FILE =
43         new File("/sys/devices/soc/624000.ufshc/health");
44 
45     private File mFile;
46 
UfsWearInformationProvider()47     public UfsWearInformationProvider() {
48         this(DEFAULT_FILE);
49     }
50 
51     @VisibleForTesting
UfsWearInformationProvider(@onNull File file)52     public UfsWearInformationProvider(@NonNull File file) {
53         mFile = file;
54     }
55 
56     @Nullable
57     @Override
load()58     public WearInformation load() {
59         if (!mFile.exists() || !mFile.isFile()) {
60             Slog.i(CarLog.TAG_STORAGE, mFile + " does not exist or is not a file");
61             return null;
62         }
63         List<String> lifetimeData;
64         try {
65             lifetimeData = java.nio.file.Files.readAllLines(mFile.toPath());
66         } catch (IOException e) {
67             Slog.w(CarLog.TAG_STORAGE, "error reading " + mFile, e);
68             return null;
69         }
70         if (lifetimeData == null || lifetimeData.size() < 4) {
71             return null;
72         }
73 
74         Pattern infoPattern = Pattern.compile(
75                 "Health Descriptor\\[Byte offset 0x\\d+\\]: (\\w+) = 0x([0-9a-fA-F]+)");
76 
77         Optional<Integer> lifetimeA = Optional.empty();
78         Optional<Integer> lifetimeB = Optional.empty();
79         Optional<Integer> eol = Optional.empty();
80 
81         for(String lifetimeInfo : lifetimeData) {
82             Scanner scanner = new Scanner(lifetimeInfo);
83             if (null == scanner.findInLine(infoPattern)) {
84                 continue;
85             }
86             MatchResult match = scanner.match();
87             if (match.groupCount() != 2) {
88                 continue;
89             }
90             String name = match.group(1);
91             String value = "0x" + match.group(2);
92             try {
93                 switch (name) {
94                     case "bPreEOLInfo":
95                         eol = Optional.of(Integer.decode(value));
96                         break;
97                     case "bDeviceLifeTimeEstA":
98                         lifetimeA = Optional.of(Integer.decode(value));
99                         break;
100                     case "bDeviceLifeTimeEstB":
101                         lifetimeB = Optional.of(Integer.decode(value));
102                         break;
103                 }
104             } catch (NumberFormatException e) {
105                 Slog.w(CarLog.TAG_STORAGE,
106                     "trying to decode key " + name + " value " + value + " didn't parse properly", e);
107             }
108         }
109 
110         if (!lifetimeA.isPresent() || !lifetimeB.isPresent() || !eol.isPresent()) {
111             return null;
112         }
113 
114         return new WearInformation(convertLifetime(lifetimeA.get()),
115             convertLifetime(lifetimeB.get()),
116             adjustEol(eol.get()));
117     }
118 }
119