blob: 3d0f0c20c6c714bbba48ae7abf3ddd3ea469a2fb [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This tool is used for getting dmesg information through debugd.
#include "debugd/src/drm_trace_tool.h"
#include <optional>
#include <utility>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/time/time.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_util.h>
#include <brillo/files/safe_fd.h>
#include <debugd/src/error_utils.h>
namespace debugd {
namespace {
// Categories copied from drm_debug_category:
// https://elixir.bootlin.com/linux/v5.14.12/source/include/drm/drm_print.h#L277
// These are not exposed in libdrm or other userspace headers, so we need to
// duplicate them here.
enum DRMDebugCategories {
DRM_UT_CORE = 0x01,
DRM_UT_DRIVER = 0x02,
DRM_UT_KMS = 0x04,
DRM_UT_PRIME = 0x08,
DRM_UT_ATOMIC = 0x10,
DRM_UT_VBL = 0x20,
DRM_UT_STATE = 0x40,
DRM_UT_LEASE = 0x80,
DRM_UT_DP = 0x100,
DRM_UT_DRMRES = 0x200,
};
constexpr bool AreEqual(DRMDebugCategories c1, DRMTraceCategories c2) {
typedef std::underlying_type<DRMDebugCategories>::type t1;
typedef std::underlying_type<DRMTraceCategories>::type t2;
return static_cast<t1>(c1) == static_cast<t2>(c2);
}
static_assert(AreEqual(DRM_UT_CORE, DRMTraceCategory_CORE));
static_assert(AreEqual(DRM_UT_DRIVER, DRMTraceCategory_DRIVER));
static_assert(AreEqual(DRM_UT_KMS, DRMTraceCategory_KMS));
static_assert(AreEqual(DRM_UT_PRIME, DRMTraceCategory_PRIME));
static_assert(AreEqual(DRM_UT_ATOMIC, DRMTraceCategory_ATOMIC));
static_assert(AreEqual(DRM_UT_VBL, DRMTraceCategory_VBL));
static_assert(AreEqual(DRM_UT_STATE, DRMTraceCategory_STATE));
static_assert(AreEqual(DRM_UT_LEASE, DRMTraceCategory_LEASE));
static_assert(AreEqual(DRM_UT_DP, DRMTraceCategory_DP));
static_assert(AreEqual(DRM_UT_DRMRES, DRMTraceCategory_DRMRES));
constexpr uint32_t kDefaultMask = DRM_UT_DRIVER | DRM_UT_KMS | DRM_UT_DP;
constexpr uint32_t kAllCategories = 0x3FF;
constexpr uint32_t kDefaultTraceBufferSizeKb = 64;
// 2MB * num_cpus. This is somewhat arbitrary. Increase in size if we need more.
constexpr uint32_t kDebugTraceBufferSizeKb = 2 * 1024;
// 256K, to account for large blocks of text such as modetest output.
constexpr uint32_t kMaxLogSize = 256 * 1024;
// Drop the first slash since the root path can be set for testing.
constexpr char kTraceMaskFile[] = "sys/module/drm/parameters/trace";
constexpr char kTraceBufferSizeFile[] =
"sys/kernel/debug/tracing/instances/drm/buffer_size_kb";
constexpr char kTraceMarkerFile[] =
"sys/kernel/debug/tracing/instances/drm/trace_marker";
constexpr char kSnapshotDirPath[] = "var/log/display_debug";
constexpr char kDrmTraceLogName[] = "drm_trace";
constexpr char kModetestLogName[] = "modetest";
constexpr char kDRMTraceToolErrorString[] =
"org.chromium.debugd.error.DRMTrace";
// Convert |size| to the corresponding DRMTraceSizes enum value. Returns
// false if |size| is not a valid enum value.
bool ConvertSize(uint32_t size, DRMTraceSizes* out_size) {
if (size == DRMTraceSize_DEFAULT)
*out_size = DRMTraceSize_DEFAULT;
else if (size == DRMTraceSize_DEBUG)
*out_size = DRMTraceSize_DEBUG;
else
return false;
return true;
}
// Convert |type| to the corresponding DRMSnapshotType enum value. Returns
// false if |type| is not a valid enum value.
bool ConvertType(uint32_t type, DRMSnapshotType* out_type) {
switch (type) {
case DRMSnapshotType_TRACE:
*out_type = DRMSnapshotType_TRACE;
return true;
case DRMSnapshotType_MODETEST:
*out_type = DRMSnapshotType_MODETEST;
return true;
default:
return false;
}
}
base::FilePath GenerateSnapshotFilePath(const char* base_name) {
base::Time now = base::Time::Now();
base::Time::Exploded exploded;
now.LocalExplode(&exploded);
// var/log/blah/trace.YYYYMMDD-HHMMSS
return base::FilePath(kSnapshotDirPath)
.Append(base::FilePath(base::StringPrintf(
"%s.%04d%02d%02d-%02d%02d%02d", base_name, exploded.year,
exploded.month, exploded.day_of_month, exploded.hour, exploded.minute,
exploded.second)));
}
} // namespace
DRMTraceTool::DRMTraceTool(LogProvider* log_provider)
: DRMTraceTool(base::FilePath("/"), log_provider) {}
DRMTraceTool::DRMTraceTool(const base::FilePath& root_path,
LogProvider* log_provider)
: root_path_(root_path), log_provider_(log_provider) {
// Ensure that the DRM trace parameters are initialized to default when debugd
// starts.
SetToDefault();
}
bool DRMTraceTool::SetCategories(brillo::ErrorPtr* error, uint32_t categories) {
base::FilePath mask_path = root_path_.Append(kTraceMaskFile);
if (categories & ~kAllCategories) {
DEBUGD_ADD_ERROR_FMT(error, kDRMTraceToolErrorString,
"Unknown category flags: 0x%x",
categories & ~kAllCategories);
return false;
}
// Flags for categories match the flags expected by the kernel for drm_trace,
// as asserted above.
uint32_t mask = categories;
if (categories == 0)
mask = kDefaultMask;
return WriteToFile(error, mask_path, base::StringPrintf("%d", mask));
}
bool DRMTraceTool::SetSize(brillo::ErrorPtr* error, uint32_t size_enum) {
base::FilePath size_path = root_path_.Append(kTraceBufferSizeFile);
DRMTraceSizes drm_trace_size;
if (!ConvertSize(size_enum, &drm_trace_size)) {
DEBUGD_ADD_ERROR_FMT(error, kDRMTraceToolErrorString,
"Invalid value for size: %u", size_enum);
return false;
}
uint32_t size_kb = kDefaultTraceBufferSizeKb;
if (drm_trace_size == DRMTraceSize_DEBUG)
size_kb = kDebugTraceBufferSizeKb;
return WriteToFile(error, size_path, base::StringPrintf("%d", size_kb));
}
bool DRMTraceTool::AnnotateLog(brillo::ErrorPtr* error,
const std::string& log) {
base::FilePath marker_path = root_path_.Append(kTraceMarkerFile);
// Ensure the string is a reasonable size.
if (log.size() >= kMaxLogSize) {
DEBUGD_ADD_ERROR(error, kDRMTraceToolErrorString, "Log too large.");
return false;
}
// Sanitize the log. Allow only ascii printable characters and whitespace
// (which will include newlines). Invalid characters will be replaced
// with '_'.
const char kReplacementChar = '_';
std::string sanitized_log;
sanitized_log.resize(log.size());
std::replace_copy_if(
log.begin(), log.end(), sanitized_log.begin(),
[](auto c) {
return !(base::IsAsciiPrintable(c) || base::IsAsciiWhitespace(c));
},
kReplacementChar);
return WriteToFile(error, marker_path, sanitized_log);
}
bool DRMTraceTool::Snapshot(brillo::ErrorPtr* error, uint32_t type_enum) {
DRMSnapshotType drm_snapshot_type;
if (!ConvertType(type_enum, &drm_snapshot_type)) {
DEBUGD_ADD_ERROR_FMT(error, kDRMTraceToolErrorString,
"Invalid value for type: %u", type_enum);
return false;
}
std::string log_name;
switch (drm_snapshot_type) {
case DRMSnapshotType_TRACE:
log_name = kDrmTraceLogName;
break;
case DRMSnapshotType_MODETEST: {
log_name = kModetestLogName;
break;
}
}
std::optional<std::string> log_contents = log_provider_->GetLog(log_name);
if (!log_contents.has_value()) {
DEBUGD_ADD_ERROR_FMT(error, kDRMTraceToolErrorString,
"Failed to get named log: : %s", log_name.c_str());
return false;
}
const base::FilePath snapshot_path =
root_path_.Append(GenerateSnapshotFilePath(log_name.c_str()));
// Create the empty snapshot file to copy the log contents into.
brillo::SafeFD::SafeFDResult result = brillo::SafeFD::Root();
if (!brillo::SafeFD::IsError(result.second))
result = result.first.MakeFile(snapshot_path);
if (brillo::SafeFD::IsError(result.second)) {
DEBUGD_ADD_ERROR_FMT(error, kDRMTraceToolErrorString,
"Failed to create snapshot file: : %s",
snapshot_path.value().c_str());
return false;
}
return WriteToFile(error, snapshot_path, log_contents.value());
}
bool DRMTraceTool::WriteToFile(brillo::ErrorPtr* error,
const base::FilePath& path,
const std::string& contents) {
brillo::SafeFD::SafeFDResult result = brillo::SafeFD::Root();
if (brillo::SafeFD::IsError(result.second)) {
DEBUGD_ADD_ERROR_FMT(error, kDRMTraceToolErrorString,
"Failed to open SafeFD::Root(), error: %d",
result.second);
return false;
}
result = result.first.OpenExistingFile(path);
if (brillo::SafeFD::IsError(result.second)) {
DEBUGD_ADD_ERROR_FMT(error, kDRMTraceToolErrorString,
"Failed to open %s, error: %d", path.value().c_str(),
result.second);
return false;
}
// SafeFD::Replace is not used here because
// /sys/kernel/debug/tracing/instances/drm/trace_marker
// does not support seek (b/227339558).
brillo::SafeFD::Error safefd_error =
result.first.Write(contents.c_str(), contents.size());
if (brillo::SafeFD::IsError(safefd_error)) {
DEBUGD_ADD_ERROR_FMT(error, kDRMTraceToolErrorString,
"Failed to write to %s, error: %d",
path.value().c_str(), safefd_error);
return false;
}
return true;
}
void DRMTraceTool::OnSessionStarted() {
SetToDefault();
}
void DRMTraceTool::OnSessionStopped() {
SetToDefault();
}
void DRMTraceTool::SetToDefault() {
brillo::ErrorPtr error;
if (!SetCategories(&error, 0))
LOG(WARNING) << "Failed to reset categories; drm_trace may have unexpected "
"log entries.";
if (!SetSize(&error, DRMTraceSize_DEFAULT))
LOG(WARNING) << "Failed to reset trace buffer size; drm_trace may be "
"larger than expected.";
}
} // namespace debugd