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