diff --git a/wifi/1.2/default/Android.mk b/wifi/1.2/default/Android.mk index 95414bc743..8d0262b758 100644 --- a/wifi/1.2/default/Android.mk +++ b/wifi/1.2/default/Android.mk @@ -30,6 +30,7 @@ endif LOCAL_SRC_FILES := \ hidl_struct_util.cpp \ hidl_sync_util.cpp \ + ringbuffer.cpp \ wifi.cpp \ wifi_ap_iface.cpp \ wifi_chip.cpp \ @@ -97,6 +98,7 @@ LOCAL_SRC_FILES := \ tests/mock_wifi_feature_flags.cpp \ tests/mock_wifi_legacy_hal.cpp \ tests/mock_wifi_mode_controller.cpp \ + tests/ringbuffer_unit_tests.cpp \ tests/wifi_chip_unit_tests.cpp LOCAL_STATIC_LIBRARIES := \ libgmock \ diff --git a/wifi/1.2/default/ringbuffer.cpp b/wifi/1.2/default/ringbuffer.cpp new file mode 100644 index 0000000000..5511f2ffba --- /dev/null +++ b/wifi/1.2/default/ringbuffer.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ringbuffer.h" + +namespace android { +namespace hardware { +namespace wifi { +namespace V1_2 { +namespace implementation { + +Ringbuffer::Ringbuffer(size_t maxSize) : size_(0), maxSize_(maxSize) {} + +void Ringbuffer::append(const std::vector& input) { + if (input.size() == 0) { + return; + } + data_.push_back(input); + size_ += input.size() * sizeof(input[0]); + while (size_ > maxSize_) { + size_ -= data_.front().size() * sizeof(data_.front()[0]); + data_.pop_front(); + } +} + +const std::list>& Ringbuffer::getData() const { + return data_; +} + +} // namespace implementation +} // namespace V1_2 +} // namespace wifi +} // namespace hardware +} // namespace android diff --git a/wifi/1.2/default/ringbuffer.h b/wifi/1.2/default/ringbuffer.h new file mode 100644 index 0000000000..4808e40b78 --- /dev/null +++ b/wifi/1.2/default/ringbuffer.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RINGBUFFER_H_ +#define RINGBUFFER_H_ + +#include +#include + +namespace android { +namespace hardware { +namespace wifi { +namespace V1_2 { +namespace implementation { + +/** + * Ringbuffer object used to store debug data. + */ +class Ringbuffer { + public: + explicit Ringbuffer(size_t maxSize); + + // Appends the data buffer and deletes from the front until buffer is + // within |maxSize_|. + void append(const std::vector& input); + const std::list>& getData() const; + + private: + std::list> data_; + size_t size_; + size_t maxSize_; +}; + +} // namespace implementation +} // namespace V1_2 +} // namespace wifi +} // namespace hardware +} // namespace android + +#endif // RINGBUFFER_H_ diff --git a/wifi/1.2/default/tests/ringbuffer_unit_tests.cpp b/wifi/1.2/default/tests/ringbuffer_unit_tests.cpp new file mode 100644 index 0000000000..1b332f9b8b --- /dev/null +++ b/wifi/1.2/default/tests/ringbuffer_unit_tests.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ringbuffer.h" + +using testing::Return; +using testing::Test; + +namespace android { +namespace hardware { +namespace wifi { +namespace V1_2 { +namespace implementation { + +class RingbufferTest : public Test { + public: + const uint32_t maxBufferSize_ = 10; + Ringbuffer buffer_{maxBufferSize_}; +}; + +TEST_F(RingbufferTest, CreateEmptyBuffer) { + ASSERT_TRUE(buffer_.getData().empty()); +} + +TEST_F(RingbufferTest, CanUseFullBufferCapacity) { + const std::vector input(maxBufferSize_ / 2, '0'); + const std::vector input2(maxBufferSize_ / 2, '1'); + buffer_.append(input); + buffer_.append(input2); + ASSERT_EQ(2u, buffer_.getData().size()); + EXPECT_EQ(input, buffer_.getData().front()); + EXPECT_EQ(input2, buffer_.getData().back()); +} + +TEST_F(RingbufferTest, OldDataIsRemovedOnOverflow) { + const std::vector input(maxBufferSize_ / 2, '0'); + const std::vector input2(maxBufferSize_ / 2, '1'); + const std::vector input3 = {'G'}; + buffer_.append(input); + buffer_.append(input2); + buffer_.append(input3); + ASSERT_EQ(2u, buffer_.getData().size()); + EXPECT_EQ(input2, buffer_.getData().front()); + EXPECT_EQ(input3, buffer_.getData().back()); +} + +TEST_F(RingbufferTest, MultipleOldDataIsRemovedOnOverflow) { + const std::vector input(maxBufferSize_ / 2, '0'); + const std::vector input2(maxBufferSize_ / 2, '1'); + const std::vector input3(maxBufferSize_, '2'); + buffer_.append(input); + buffer_.append(input2); + buffer_.append(input3); + ASSERT_EQ(1u, buffer_.getData().size()); + EXPECT_EQ(input3, buffer_.getData().front()); +} + +TEST_F(RingbufferTest, AppendingEmptyBufferDoesNotAddGarbage) { + const std::vector input = {}; + buffer_.append(input); + ASSERT_TRUE(buffer_.getData().empty()); +} + +TEST_F(RingbufferTest, OversizedAppendIsDropped) { + const std::vector input(maxBufferSize_ + 1, '0'); + buffer_.append(input); + ASSERT_TRUE(buffer_.getData().empty()); +} +} // namespace implementation +} // namespace V1_2 +} // namespace wifi +} // namespace hardware +} // namespace android diff --git a/wifi/1.2/default/wifi.cpp b/wifi/1.2/default/wifi.cpp index 06f5058891..d6106b47f9 100644 --- a/wifi/1.2/default/wifi.cpp +++ b/wifi/1.2/default/wifi.cpp @@ -77,6 +77,12 @@ Return Wifi::getChip(ChipId chip_id, getChip_cb hidl_status_cb) { &Wifi::getChipInternal, hidl_status_cb, chip_id); } +Return Wifi::debug(const hidl_handle& handle, + const hidl_vec&) { + LOG(INFO) << "-----------Debug is called----------------"; + return chip_->debug(handle, {}); +} + WifiStatus Wifi::registerEventCallbackInternal( const sp& event_callback) { if (!event_cb_handler_.addCallback(event_callback)) { diff --git a/wifi/1.2/default/wifi.h b/wifi/1.2/default/wifi.h index 440c3c7984..86919b194e 100644 --- a/wifi/1.2/default/wifi.h +++ b/wifi/1.2/default/wifi.h @@ -56,6 +56,8 @@ class Wifi : public V1_2::IWifi { Return stop(stop_cb hidl_status_cb) override; Return getChipIds(getChipIds_cb hidl_status_cb) override; Return getChip(ChipId chip_id, getChip_cb hidl_status_cb) override; + Return debug(const hidl_handle& handle, + const hidl_vec& options) override; private: enum class RunState { STOPPED, STARTED, STOPPING }; diff --git a/wifi/1.2/default/wifi_chip.cpp b/wifi/1.2/default/wifi_chip.cpp index 8d9cfc6188..bc3404a819 100644 --- a/wifi/1.2/default/wifi_chip.cpp +++ b/wifi/1.2/default/wifi_chip.cpp @@ -14,8 +14,13 @@ * limitations under the License. */ +#include + #include +#include #include +#include +#include #include "hidl_return_util.h" #include "hidl_struct_util.h" @@ -23,6 +28,7 @@ #include "wifi_status_util.h" namespace { +using android::base::unique_fd; using android::hardware::hidl_string; using android::hardware::hidl_vec; using android::hardware::wifi::V1_0::ChipModeId; @@ -39,6 +45,11 @@ constexpr ChipModeId kV1ApChipModeId = 1; // Mode ID for V2 constexpr ChipModeId kV2ChipModeId = 2; +constexpr char kCpioMagic[] = "070701"; +constexpr size_t kMaxBufferSizeBytes = 1024 * 1024; +constexpr uint32_t kMaxRingBufferFileAgeSeconds = 60 * 60; +constexpr char kTombstoneFolderPath[] = "/data/vendor/tombstones/wifi/"; + template void invalidateAndClear(std::vector>& ifaces, sp iface) { iface->invalidate(); @@ -93,6 +104,165 @@ std::string getP2pIfaceName() { return buffer.data(); } +// delete files older than a predefined time in the wifi tombstone dir +bool removeOldFilesInternal() { + time_t now = time(0); + const time_t delete_files_before = now - kMaxRingBufferFileAgeSeconds; + DIR* dir_dump = opendir(kTombstoneFolderPath); + if (!dir_dump) { + LOG(ERROR) << "Failed to open directory: " << strerror(errno); + return false; + } + unique_fd dir_auto_closer(dirfd(dir_dump)); + struct dirent* dp; + bool success = true; + while ((dp = readdir(dir_dump))) { + if (dp->d_type != DT_REG) { + continue; + } + std::string cur_file_name(dp->d_name); + struct stat cur_file_stat; + std::string cur_file_path = kTombstoneFolderPath + cur_file_name; + if (stat(cur_file_path.c_str(), &cur_file_stat) != -1) { + if (cur_file_stat.st_mtime < delete_files_before) { + if (unlink(cur_file_path.c_str()) != 0) { + LOG(ERROR) << "Error deleting file " << strerror(errno); + success = false; + } + } + } else { + LOG(ERROR) << "Failed to get file stat for " << cur_file_path + << ": " << strerror(errno); + success = false; + } + } + return success; +} + +// Archives all files in |input_dir| and writes result into |out_fd| +// Logic obtained from //external/toybox/toys/posix/cpio.c "Output cpio archive" +// portion +size_t cpioFilesInDir(int out_fd, const char* input_dir) { + struct dirent* dp; + size_t n_error = 0; + char read_buf[32 * 1024]; + DIR* dir_dump = opendir(input_dir); + if (!dir_dump) { + LOG(ERROR) << "Failed to open directory: " << strerror(errno); + n_error++; + return n_error; + } + unique_fd dir_auto_closer(dirfd(dir_dump)); + while ((dp = readdir(dir_dump))) { + if (dp->d_type != DT_REG) { + continue; + } + std::string cur_file_name(dp->d_name); + const size_t file_name_len = + cur_file_name.size() + 1; // string.size() does not include the + // null terminator. The cpio FreeBSD file + // header expects the null character to + // be included in the length. + struct stat st; + ssize_t llen; + const std::string cur_file_path = kTombstoneFolderPath + cur_file_name; + if (stat(cur_file_path.c_str(), &st) != -1) { + const int fd_read = open(cur_file_path.c_str(), O_RDONLY); + unique_fd file_auto_closer(fd_read); + if (fd_read == -1) { + LOG(ERROR) << "Failed to read file " << cur_file_path << " " + << strerror(errno); + n_error++; + continue; + } + llen = sprintf( + read_buf, + "%s%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X", + kCpioMagic, static_cast(st.st_ino), st.st_mode, st.st_uid, + st.st_gid, static_cast(st.st_nlink), + static_cast(st.st_mtime), static_cast(st.st_size), + major(st.st_dev), minor(st.st_dev), major(st.st_rdev), + minor(st.st_rdev), static_cast(file_name_len), 0); + if (write(out_fd, read_buf, llen) == -1) { + LOG(ERROR) << "Error writing cpio header to file " + << cur_file_path << " " << strerror(errno); + n_error++; + return n_error; + } + if (write(out_fd, cur_file_name.c_str(), file_name_len) == -1) { + LOG(ERROR) << "Error writing filename to file " << cur_file_path + << " " << strerror(errno); + n_error++; + return n_error; + } + + // NUL Pad header up to 4 multiple bytes. + llen = (llen + file_name_len) % 4; + if (llen != 0) { + const uint32_t zero = 0; + if (write(out_fd, &zero, 4 - llen) == -1) { + LOG(ERROR) << "Error padding 0s to file " << cur_file_path + << " " << strerror(errno); + n_error++; + return n_error; + } + } + + // writing content of file + llen = st.st_size; + while (llen > 0) { + ssize_t bytes_read = read(fd_read, read_buf, sizeof(read_buf)); + if (bytes_read == -1) { + LOG(ERROR) << "Error reading file " << cur_file_path << " " + << strerror(errno); + n_error++; + return n_error; + } + llen -= bytes_read; + if (write(out_fd, read_buf, bytes_read) == -1) { + LOG(ERROR) << "Error writing data to file " << cur_file_path + << " " << strerror(errno); + n_error++; + return n_error; + } + if (bytes_read == + 0) { // this should never happen, but just in case + // to unstuck from while loop + LOG(ERROR) << "Unexpected file size for " << cur_file_path + << " " << strerror(errno); + n_error++; + break; + } + } + llen = st.st_size % 4; + if (llen != 0) { + const uint32_t zero = 0; + write(out_fd, &zero, 4 - llen); + } + } else { + LOG(ERROR) << "Failed to get file stat for " << cur_file_path + << ": " << strerror(errno); + n_error++; + } + } + memset(read_buf, 0, sizeof(read_buf)); + if (write(out_fd, read_buf, + sprintf(read_buf, "070701%040X%056X%08XTRAILER!!!", 1, 0x0b, 0) + + 4) == -1) { + LOG(ERROR) << "Error writing trailing bytes " << strerror(errno); + n_error++; + } + return n_error; +} + +// Helper function to create a non-const char*. +std::vector makeCharVec(const std::string& str) { + std::vector vec(str.size() + 1); + vec.assign(str.begin(), str.end()); + vec.push_back('\0'); + return vec; +} + } // namespace namespace android { @@ -350,6 +520,24 @@ Return WifiChip::resetTxPowerScenario( hidl_status_cb); } +Return WifiChip::debug(const hidl_handle& handle, + const hidl_vec&) { + if (handle != nullptr && handle->numFds >= 1) { + int fd = handle->data[0]; + if (!writeRingbufferFilesInternal()) { + LOG(ERROR) << "Error writing files to flash"; + } + uint32_t n_error = cpioFilesInDir(fd, kTombstoneFolderPath); + if (n_error != 0) { + LOG(ERROR) << n_error << " errors occured in cpio function"; + } + fsync(fd); + } else { + LOG(ERROR) << "File handle error"; + } + return Void(); +} + void WifiChip::invalidateAndRemoveAllIfaces() { invalidateAndClearAll(ap_ifaces_); invalidateAndClearAll(nan_ifaces_); @@ -727,6 +915,8 @@ WifiStatus WifiChip::startLoggingToDebugRingBufferInternal( std::underlying_type::type>( verbose_level), max_interval_in_sec, min_data_size_in_bytes); + ringbuffer_map_.insert(std::pair( + ring_name, Ringbuffer(kMaxBufferSizeBytes))); return createWifiStatusFromLegacyError(legacy_status); } @@ -738,6 +928,7 @@ WifiStatus WifiChip::forceDumpToDebugRingBufferInternal( } legacy_hal::wifi_error legacy_status = legacy_hal_.lock()->getRingBufferData(getWlan0IfaceName(), ring_name); + return createWifiStatusFromLegacyError(legacy_status); } @@ -849,7 +1040,7 @@ WifiStatus WifiChip::registerDebugRingBufferCallback() { android::wp weak_ptr_this(this); const auto& on_ring_buffer_data_callback = - [weak_ptr_this](const std::string& /* name */, + [weak_ptr_this](const std::string& name, const std::vector& data, const legacy_hal::wifi_ring_buffer_status& status) { const auto shared_ptr_this = weak_ptr_this.promote(); @@ -863,13 +1054,13 @@ WifiStatus WifiChip::registerDebugRingBufferCallback() { LOG(ERROR) << "Error converting ring buffer status"; return; } - for (const auto& callback : shared_ptr_this->getEventCallbacks()) { - if (!callback->onDebugRingBufferDataAvailable(hidl_status, data) - .isOk()) { - LOG(ERROR) - << "Failed to invoke onDebugRingBufferDataAvailable" - << " callback on: " << toString(callback); - } + const auto& target = shared_ptr_this->ringbuffer_map_.find(name); + if (target != shared_ptr_this->ringbuffer_map_.end()) { + Ringbuffer& cur_buffer = target->second; + cur_buffer.append(data); + } else { + LOG(ERROR) << "Ringname " << name << " not found"; + return; } }; legacy_hal::wifi_error legacy_status = @@ -1080,6 +1271,35 @@ std::string WifiChip::allocateApOrStaIfaceName() { return {}; } +bool WifiChip::writeRingbufferFilesInternal() { + if (!removeOldFilesInternal()) { + LOG(ERROR) << "Error occurred while deleting old tombstone files"; + return false; + } + // write ringbuffers to file + for (const auto& item : ringbuffer_map_) { + const Ringbuffer& cur_buffer = item.second; + if (cur_buffer.getData().empty()) { + continue; + } + const std::string file_path_raw = + kTombstoneFolderPath + item.first + "XXXXXXXXXX"; + const int dump_fd = mkstemp(makeCharVec(file_path_raw).data()); + if (dump_fd == -1) { + LOG(ERROR) << "create file failed: " << strerror(errno); + return false; + } + unique_fd file_auto_closer(dump_fd); + for (const auto& cur_block : cur_buffer.getData()) { + if (write(dump_fd, cur_block.data(), + sizeof(cur_block[0]) * cur_block.size()) == -1) { + LOG(ERROR) << "Error writing to file " << strerror(errno); + } + } + } + return true; +} + } // namespace implementation } // namespace V1_2 } // namespace wifi diff --git a/wifi/1.2/default/wifi_chip.h b/wifi/1.2/default/wifi_chip.h index b5dcc8cb17..4ffa8da138 100644 --- a/wifi/1.2/default/wifi_chip.h +++ b/wifi/1.2/default/wifi_chip.h @@ -17,12 +17,14 @@ #ifndef WIFI_CHIP_H_ #define WIFI_CHIP_H_ +#include #include #include #include #include "hidl_callback_util.h" +#include "ringbuffer.h" #include "wifi_ap_iface.h" #include "wifi_feature_flags.h" #include "wifi_legacy_hal.h" @@ -134,6 +136,8 @@ class WifiChip : public V1_2::IWifiChip { selectTxPowerScenario_cb hidl_status_cb) override; Return resetTxPowerScenario( resetTxPowerScenario_cb hidl_status_cb) override; + Return debug(const hidl_handle& handle, + const hidl_vec& options) override; private: void invalidateAndRemoveAllIfaces(); @@ -204,6 +208,7 @@ class WifiChip : public V1_2::IWifiChip { bool canCurrentModeSupportIfaceOfType(IfaceType type); bool isValidModeId(ChipModeId mode_id); std::string allocateApOrStaIfaceName(); + bool writeRingbufferFilesInternal(); ChipId chip_id_; std::weak_ptr legacy_hal_; @@ -214,6 +219,7 @@ class WifiChip : public V1_2::IWifiChip { std::vector> p2p_ifaces_; std::vector> sta_ifaces_; std::vector> rtt_controllers_; + std::map ringbuffer_map_; bool is_valid_; // Members pertaining to chip configuration. uint32_t current_mode_id_;