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