blob: 117d4ac37ccec8df6ada395ca17748d95289b8c9 [file]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "debugd/src/firmware_dump_utils.h"
#include <stdint.h>
#include <utility>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/test/mock_log.h>
#include <brillo/dbus/dbus_method_response.h>
#include <brillo/dbus/dbus_object_test_helpers.h>
#include <brillo/dbus/mock_dbus_method_response.h>
#include <brillo/files/file_util.h>
#include <chromeos/dbus/debugd/dbus-constants.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "debugd/src/debugd_dbus_adaptor.h"
#include "debugd/src/path_utils.h"
using ::testing::_;
using ::testing::AtLeast;
namespace {
// Builds a mocked D-Bus response for async methods. Regarding the failure and
// success cases, below are the usages for testing:
// 1. The failure cases (|ReplyWithError|) are well-mocked and can be set as
// expectations directly.
// 2. The success cases (|Return| with no error) are not mocked, but |Return| is
// overridden to store the output in |response| so that the caller can set
// expectations on |response| later.
template <typename T>
std::unique_ptr<brillo::dbus_utils::MockDBusMethodResponse<T>>
BuildMockDBusResponse(T* response) {
auto dbus_response =
std::make_unique<brillo::dbus_utils::MockDBusMethodResponse<T>>(
/*method_call = nullptr*/);
dbus_response->set_return_callback(
base::BindOnce([](T* response_out,
const T& response_in) { *response_out = response_in; },
response));
return dbus_response;
}
} // namespace
namespace debugd {
class FirmwareDumpUtilsTest : public ::testing::Test {
protected:
void CreateDirectory(const base::FilePath& dir) {
base::File::Error error;
ASSERT_TRUE(base::CreateDirectoryAndGetError(dir, &error))
<< base::File::ErrorToString(error);
}
// Create the debugfs file and all its parent directories.
void CreateDebugfsFile(std::string_view dumper) {
const base::FilePath debugfs_path = path_utils::GetFilePath(dumper);
ASSERT_TRUE(brillo::DeleteFile(debugfs_path));
CreateDirectory(debugfs_path.DirName());
ASSERT_TRUE(base::WriteFile(debugfs_path, ""));
}
private:
void SetUp() override {
CHECK(tmp_dir_.CreateUniqueTempDir()) << "Fail to create temp directory";
path_utils::testing::SetPrefixForTesting(tmp_dir_.GetPath());
}
void TearDown() override {
// Reset the prefix after test.
path_utils::testing::SetPrefixForTesting(base::FilePath());
}
base::ScopedTempDir tmp_dir_;
};
TEST_F(FirmwareDumpUtilsTest, ConvertFirmwareDumpType) {
const auto fwdump_type_all = ConvertFirmwareDumpType(0);
EXPECT_TRUE(fwdump_type_all.has_value());
EXPECT_EQ(FirmwareDumpType::ALL, fwdump_type_all.value());
const auto fwdump_type_wifi = ConvertFirmwareDumpType(1);
EXPECT_TRUE(fwdump_type_wifi.has_value());
EXPECT_EQ(FirmwareDumpType::WIFI, fwdump_type_wifi.value());
const auto fwdump_type_invalid = ConvertFirmwareDumpType(UINT32_MAX);
EXPECT_FALSE(fwdump_type_invalid.has_value());
}
TEST_F(FirmwareDumpUtilsTest, FindDebugfsPathSuccess) {
CreateDebugfsFile(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect");
auto dumper_path = FindDebugfsPath(
FirmwareDumpType::WIFI, FirmwareDumpOperation::GenerateFirmwareDump);
EXPECT_TRUE(dumper_path.has_value());
EXPECT_EQ(path_utils::GetFilePath(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect")
.value(),
dumper_path->value());
}
TEST_F(FirmwareDumpUtilsTest, FindDebugfsPathNoBaseDirectoryForType) {
CreateDebugfsFile(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect");
base::test::MockLog log;
log.StartCapturingLogs();
EXPECT_CALL(log, Log(::logging::LOGGING_ERROR, _, _, _, _)).Times(AtLeast(1));
auto dumper_path =
FindDebugfsPath(static_cast<FirmwareDumpType>(UINT32_MAX),
FirmwareDumpOperation::GenerateFirmwareDump);
log.StopCapturingLogs();
EXPECT_FALSE(dumper_path.has_value());
}
TEST_F(FirmwareDumpUtilsTest, FindDebugfsPathNoBaseDirectoryOnFilesystem) {
CreateDebugfsFile("/sys/kernel/debug/path/to/fake/file");
base::test::MockLog log;
log.StartCapturingLogs();
EXPECT_CALL(log, Log(::logging::LOGGING_ERROR, _, _, _, _)).Times(AtLeast(1));
auto dumper_path = FindDebugfsPath(
FirmwareDumpType::WIFI, FirmwareDumpOperation::GenerateFirmwareDump);
log.StopCapturingLogs();
EXPECT_FALSE(dumper_path.has_value());
}
TEST_F(FirmwareDumpUtilsTest, FindDebugfsPathNoFirmwareDumpOperationFile) {
CreateDebugfsFile(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect");
base::test::MockLog log;
log.StartCapturingLogs();
EXPECT_CALL(log, Log(::logging::LOGGING_ERROR, _, _, _, _)).Times(AtLeast(1));
auto dumper_path = FindDebugfsPath(
FirmwareDumpType::WIFI, static_cast<FirmwareDumpOperation>(UINT32_MAX));
log.StopCapturingLogs();
EXPECT_FALSE(dumper_path.has_value());
}
TEST_F(FirmwareDumpUtilsTest, FindDebugfsPathEmptyBaseDirectory) {
CreateDirectory(path_utils::GetFilePath("/sys/kernel/debug/iwlwifi"));
base::test::MockLog log;
log.StartCapturingLogs();
EXPECT_CALL(log, Log(::logging::LOGGING_ERROR, _, _, _, _)).Times(AtLeast(1));
auto dumper_path = FindDebugfsPath(
FirmwareDumpType::WIFI, FirmwareDumpOperation::GenerateFirmwareDump);
log.StopCapturingLogs();
EXPECT_FALSE(dumper_path.has_value());
}
TEST_F(FirmwareDumpUtilsTest, FindDebugfsPathMultipleSubdirectory) {
// Normally there is only one PCIe device for a particular WiFi driver ("/sys/
// kernel/debug/iwlwifi/" in the following test case) and one sub-directory
// ("0000:00:14.3/iwlmvm/") for a collection of unique debugfs files.
// |FindDebugfsPath| is based on this assumption and exits the sub-directory
// search loop once the targeted debugfs file is found. This test case makes
// sure |FindDebugfsPath| can find the targeted debugfs file when there are
// multiple sub-directories, but doesn't cover the case where there are more
// than one debugfs files with the same name under different sub-directories.
// If this assumption no longer holds for other device driver, modifications
// on the search strategy in |FindDebugfsPath| are required.
CreateDebugfsFile("/sys/kernel/debug/iwlwifi/0000:00:14.2/fake/file");
CreateDebugfsFile(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect");
auto dumper_path = FindDebugfsPath(
FirmwareDumpType::WIFI, FirmwareDumpOperation::GenerateFirmwareDump);
EXPECT_TRUE(dumper_path.has_value());
EXPECT_EQ(path_utils::GetFilePath(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect")
.value(),
dumper_path->value());
}
TEST_F(FirmwareDumpUtilsTest, FindDebugfsPathNoDumperFile) {
CreateDebugfsFile("/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fake_file");
base::test::MockLog log;
log.StartCapturingLogs();
EXPECT_CALL(log, Log(::logging::LOGGING_ERROR, _, _, _, _)).Times(AtLeast(1));
auto dumper_path = FindDebugfsPath(
FirmwareDumpType::WIFI, FirmwareDumpOperation::GenerateFirmwareDump);
log.StopCapturingLogs();
EXPECT_FALSE(dumper_path.has_value());
}
TEST_F(FirmwareDumpUtilsTest, WriteToDebugfsSuccess) {
CreateDebugfsFile(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect");
const auto dumper_path = FindDebugfsPath(
FirmwareDumpType::WIFI, FirmwareDumpOperation::GenerateFirmwareDump);
EXPECT_TRUE(WriteToDebugfs(dumper_path.value(), "1"));
std::string content;
EXPECT_TRUE(base::ReadFileToString(
path_utils::GetFilePath(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect"),
&content));
EXPECT_EQ("1", content);
}
TEST_F(FirmwareDumpUtilsTest, WriteToDebugfsFailToWrite) {
CreateDebugfsFile(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect");
// Remove the write permission to the debugfs file to mimic write failure.
ASSERT_TRUE(SetPosixFilePermissions(
path_utils::GetFilePath(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect"),
0400))
<< "Fail to remove write permission to the debugfs file";
base::test::MockLog log;
log.StartCapturingLogs();
EXPECT_CALL(log, Log(::logging::LOGGING_ERROR, _, _, _, _)).Times(AtLeast(1));
const auto dumper_path = FindDebugfsPath(
FirmwareDumpType::WIFI, FirmwareDumpOperation::GenerateFirmwareDump);
EXPECT_FALSE(WriteToDebugfs(dumper_path.value(), "1"));
log.StopCapturingLogs();
}
TEST_F(FirmwareDumpUtilsTest, GenerateFirmwareDumpForWiFiSuccess) {
bool response = false;
auto mocked_method_response = BuildMockDBusResponse(&response);
CreateDebugfsFile(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect");
EXPECT_CALL(*mocked_method_response, ReplyWithError(_, _, _, _)).Times(0);
GenerateFirmwareDumpHelper(std::move(mocked_method_response),
FirmwareDumpType::WIFI);
EXPECT_TRUE(response);
std::string content;
EXPECT_TRUE(base::ReadFileToString(
path_utils::GetFilePath(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_collect"),
&content));
EXPECT_EQ("1", content);
}
TEST_F(FirmwareDumpUtilsTest, GenerateFirmwareDumpForWiFiNotSupported) {
bool response = false;
auto mocked_method_response = BuildMockDBusResponse(&response);
// "fw_dbg_collect" is not supported and doesn't exist.
CreateDebugfsFile("/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fake_file");
GenerateFirmwareDumpHelper(std::move(mocked_method_response),
FirmwareDumpType::WIFI);
// If the file does not exist, the feature is not supported and the API will
// return success.
EXPECT_TRUE(response);
}
TEST_F(FirmwareDumpUtilsTest, ClearFirmwareDumpBufferForWiFiSuccess) {
bool response = false;
auto mocked_method_response = BuildMockDBusResponse(&response);
CreateDebugfsFile(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_clear");
EXPECT_CALL(*mocked_method_response, ReplyWithError(_, _, _, _)).Times(0);
ClearFirmwareDumpBufferHelper(std::move(mocked_method_response),
FirmwareDumpType::WIFI);
EXPECT_TRUE(response);
std::string content;
EXPECT_TRUE(base::ReadFileToString(
path_utils::GetFilePath(
"/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fw_dbg_clear"),
&content));
EXPECT_EQ("1", content);
}
TEST_F(FirmwareDumpUtilsTest, ClearFirmwareDumpBufferForWiFiNotSupported) {
bool response = false;
auto mocked_method_response = BuildMockDBusResponse(&response);
// "fw_dbg_clear" is not supported and doesn't exist.
CreateDebugfsFile("/sys/kernel/debug/iwlwifi/0000:00:14.3/iwlmvm/fake_file");
ClearFirmwareDumpBufferHelper(std::move(mocked_method_response),
FirmwareDumpType::WIFI);
// If the file does not exist, the feature is not supported and the API will
// return success.
EXPECT_TRUE(response);
}
} // namespace debugd