From 6d80c42364d58f9aac6e97b8bf057dfb44b2921b Mon Sep 17 00:00:00 2001 From: Kristofer Jonsson Date: Thu, 14 Oct 2021 10:09:08 +0200 Subject: Add utility to print firmware log By default the logd will try to fetch the address of the print buffer from the device tree entry, assuming there is a device tree entry named 'print_queue'. Change-Id: Ic4750fe793f450152ba537820adc794731aaacaf --- utils/CMakeLists.txt | 3 +- utils/ethosu_logd/CMakeLists.txt | 23 ++++ utils/ethosu_logd/dev_mem.cpp | 133 ++++++++++++++++++ utils/ethosu_logd/dev_mem.hpp | 101 ++++++++++++++ utils/ethosu_logd/main.cpp | 290 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 utils/ethosu_logd/CMakeLists.txt create mode 100644 utils/ethosu_logd/dev_mem.cpp create mode 100644 utils/ethosu_logd/dev_mem.hpp create mode 100644 utils/ethosu_logd/main.cpp diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index cd50866..6581048 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Arm Limited. All rights reserved. +# Copyright (c) 2020-2021 Arm Limited. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 # @@ -16,4 +16,5 @@ # limitations under the License. # +add_subdirectory(ethosu_logd) add_subdirectory(inference_runner) diff --git a/utils/ethosu_logd/CMakeLists.txt b/utils/ethosu_logd/CMakeLists.txt new file mode 100644 index 0000000..552d6ae --- /dev/null +++ b/utils/ethosu_logd/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2021 Arm Limited. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# 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 +# +# 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. +# + +# Build executable +add_executable(ethosu_logd main.cpp dev_mem.cpp) + +# Install target +install(TARGETS ethosu_logd DESTINATION "bin") diff --git a/utils/ethosu_logd/dev_mem.cpp b/utils/ethosu_logd/dev_mem.cpp new file mode 100644 index 0000000..053cdd3 --- /dev/null +++ b/utils/ethosu_logd/dev_mem.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2021 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * 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 "dev_mem.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +namespace EthosU { +namespace DevMem { + +/**************************************************************************** + * Exception + ****************************************************************************/ + +Exception::Exception(const char *msg) : msg(msg) {} + +Exception::Exception(const std::string &msg) : msg(msg) {} + +Exception::~Exception() throw() {} + +const char *Exception::what() const throw() { + return msg.c_str(); +} + +/**************************************************************************** + * DevMem + ****************************************************************************/ + +DevMem::DevMem(uintptr_t address, size_t size) : + base(nullptr), pageMask(sysconf(_SC_PAGESIZE) - 1), pageOffset(address & pageMask), size(size) { + int fd = ::open("/dev/mem", O_RDWR | O_SYNC); + if (fd < 0) { + throw Exception("Failed to open device"); + } + + base = reinterpret_cast( + ::mmap(nullptr, pageOffset + size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, address & ~pageMask)); + if (base == MAP_FAILED) { + throw Exception("MMap failed"); + } + + ::close(fd); +} + +DevMem::~DevMem() { + ::munmap(base, pageOffset + size); +} + +void DevMem::read(char *dst, size_t length, size_t offset) { + if (offset + length > size) { + throw Exception("Read failed"); + } + + // TODO Why do not std::copy() or memcpy work? + for (size_t i = 0; i < length; i++) { + dst[i] = base[pageOffset + offset + i]; + } +} + +void DevMem::write(char *src, size_t length, size_t offset) { + if (offset + length > size) { + throw Exception("Write failed"); + } + + // TODO Why do not std::copy() or memcpy work? + for (size_t i = 0; i < length; i++) { + base[pageOffset + offset + i] = src[i]; + } +} + +/**************************************************************************** + * Log + ****************************************************************************/ + +Log::Log(uintptr_t address, size_t size) : DevMem(address, size) {} + +void Log::clear() { + LogHeader header; + read(header, 0); + + uint32_t rpos = header.write; + write(rpos, offsetof(LogHeader, read)); +} + +void Log::print() { + LogHeader header; + read(header, 0); + + if (header.size < LOG_SIZE_MIN || header.size > LOG_SIZE_MAX) { + std::string msg = "Incorrect ring buffer values. size=" + std::to_string(header.size) + + ", read=" + std::to_string(header.read) + ", write=" + std::to_string(header.write); + throw Exception(msg.c_str()); + } + + size_t rpos = header.read; + + // Skip forward if read is more than 'size' behind + if (rpos + header.size < header.write) { + rpos = header.write - header.size; + } + + while (rpos < header.write) { + char c; + size_t offset = rpos++ % header.size + sizeof(header); + read(c, offset); + std::cout << c; + } +} + +} // namespace DevMem +} // namespace EthosU diff --git a/utils/ethosu_logd/dev_mem.hpp b/utils/ethosu_logd/dev_mem.hpp new file mode 100644 index 0000000..9d3f0a8 --- /dev/null +++ b/utils/ethosu_logd/dev_mem.hpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * 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 DEV_MEM_HPP +#define DEV_MEM_HPP + +#include +#include + +namespace EthosU { +namespace DevMem { + +/**************************************************************************** + * Exception + ****************************************************************************/ + +class Exception : public std::exception { +public: + Exception(const char *msg); + Exception(const std::string &msg); + virtual ~Exception() throw(); + virtual const char *what() const throw(); + +private: + std::string msg; +}; + +/**************************************************************************** + * DevMem + ****************************************************************************/ + +class DevMem { +public: + DevMem(uintptr_t address, size_t size); + virtual ~DevMem(); + + void read(char *dst, size_t length, size_t offset); + + template + void read(T &dst, size_t offset) { + read(reinterpret_cast(&dst), sizeof(dst), offset); + } + + void write(char *src, size_t length, size_t offset); + + template + void write(T &src, size_t offset) { + write(reinterpret_cast(&src), sizeof(src), offset); + } + +private: + char *base; + const uintptr_t pageMask; + const size_t pageOffset; + const size_t size; +}; + +/**************************************************************************** + * Log + ****************************************************************************/ + +class Log : public DevMem { +public: + Log(uintptr_t address, size_t size = LOG_SIZE_MAX); + + void clear(); + void print(); + +private: + struct LogHeader { + uint32_t size; + uint32_t read; + uint32_t pad[6]; + uint32_t write; + }; + + static const size_t LOG_SIZE_MIN = 1024; + static const size_t LOG_SIZE_MAX = 1024 * 1024; + + static uintptr_t getAddress(); +}; + +} // namespace DevMem +} // namespace EthosU + +#endif /* DEV_MEM_HPP */ diff --git a/utils/ethosu_logd/main.cpp b/utils/ethosu_logd/main.cpp new file mode 100644 index 0000000..d7ff1eb --- /dev/null +++ b/utils/ethosu_logd/main.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2021 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 + * + * 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 "dev_mem.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +void help(const char *prog) { + std::cerr << "USAGE: " << prog << " [-h] [--address ADDRESS] [-c] [-C]" << std::endl << std::endl; + std::cerr << "positional argument:" << std::endl; + std::cerr << " -h, --help Show help message and exit" << std::endl; + std::cerr << " --address ADDRESS Address of ring buffer" << std::endl; + std::cerr << " -C Clear the ring buffer" << std::endl; + std::cerr << " -c Read and clear the ring buffer" << std::endl; +} + +class Path { +public: + Path(const std::string path) : path(path) {} + + Path(const char *path) : path(path) {} + + std::string string() const { + return path; + } + + const char *c_str() const { + return path.c_str(); + } + + Path operator/(const std::string &other) const { + if (other.substr(0, 1) == "/") { + return Path(other); + } else { + return Path(path + "/" + other); + } + } + + bool exists() const { + std::ifstream f = std::ifstream(path); + return !!f; + } + + std::vector find(const std::string &name) const { + std::vector files; + find(*this, name, files); + return files; + } + + Path parent() const { + return Path(path.substr(0, path.rfind("/"))); + } + +private: + const std::string path; + + static void find(const Path &path, const std::string &name, std::vector &files) { + DIR *dir = opendir(path.c_str()); + if (dir == nullptr) { + throw EthosU::DevMem::Exception(std::string("Failed to open ") + path.string()); + } + + const dirent *dentry; + while ((dentry = readdir(dir)) != nullptr) { + const std::string dname = dentry->d_name; + const Path pathname = path / dname; + + // Add path to list if it matches 'name' + if (dname == name) { + files.push_back(pathname); + } + + // Call 'find' recursively for directories + if (dname != "." && dname != ".." && dentry->d_type == DT_DIR) { + find(pathname, name, files); + } + } + + closedir(dir); + } +}; + +bool grep(const Path &path, const std::string &match) { + std::ifstream ifs(path.c_str(), std::ios_base::binary); + std::string content = std::string(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); + return content.find(match) != std::string::npos; +} + +class DTS { +public: + DTS(const Path &path) : path(path) {} + + void getRegByName(const std::string &name, uintptr_t &address, size_t &size) const { + size_t index = 0; + for (const auto &n : getString("reg-names")) { + if (n == name) { + getRegByIndex(index, address, size); + return; + } + + index++; + } + + throw EthosU::DevMem::Exception(std::string("Failed to find 'reg-name' ") + name); + } + + void getRegByIndex(const size_t index, uintptr_t &address, size_t &size) const { + size_t addressCell = getAddressCells(); + size_t sizeCell = getSizeCells(); + size_t offset = index * (addressCell + sizeCell) * 4; + + address = getInt("reg", offset, addressCell * 4); + size = getInt("reg", offset + addressCell * 4, sizeCell * 4); + } + +private: + const Path path; + + size_t getAddressCells() const { + const Path p = path / "#address-cells"; + if (!p.exists()) { + return 2; + } + + return getInt(p.string(), 0, 4); + } + + size_t getSizeCells() const { + const Path p = path / "#size-cells"; + if (!p.exists()) { + return 2; + } + + return getInt(p.string(), 0, 4); + } + + std::vector getProperty(const std::string &name) const { + const Path propertyPath = path / name; + std::ifstream ifs(propertyPath.c_str(), std::ios_base::binary); + std::vector content = + std::vector(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); + return content; + } + + std::vector getString(const std::string &name) const { + std::vector property = getProperty(name); + std::vector names; + + for (std::vector::iterator end, it = property.begin(); + (end = std::find(it, property.end(), '\0')) != property.end(); + it = end + 1) { + names.push_back(std::string(it, end)); + } + + return names; + } + + uint64_t getInt(const std::string &name, const size_t offset, const size_t size) const { + const std::vector property = getProperty(name); + + switch (size) { + case 1: + return toCpu(&property[offset]); + case 2: + return toCpu(&property[offset]); + case 4: + return toCpu(&property[offset]); + case 8: + return toCpu(&property[offset]); + default: + throw EthosU::DevMem::Exception("Illegal integer size"); + } + } + + static constexpr bool isBigEndian() { + int d = 0x12345678; + return (d & 0xf) == 1; + } + + template + static T toCpu(const char *src) { + T dst; + char *d = reinterpret_cast(&dst); + + if (isBigEndian()) { + for (size_t i = 0; i < sizeof(T); i++) { + d[i] = src[i]; + } + } else { + for (size_t i = 0; i < sizeof(T); i++) { + d[i] = src[sizeof(T) - 1 - i]; + } + } + + return dst; + } +}; + +void getAddressSizeFromDtb(uintptr_t &address, size_t &size) { + // This is the file system path to the device tree + const Path devtree = "/sys/firmware/devicetree/base"; + + // Search device tree for /**/compatible + for (const auto &path : devtree.find("compatible")) { + // Grep for "ethosu" in 'compatible' + if (grep(path, "arm,ethosu")) { + DTS dts(path.parent()); + + dts.getRegByName("print_queue", address, size); + return; + } + } + + throw EthosU::DevMem::Exception("Could not find Ethos-U device tree entry with reg-name 'print_queue'"); +} + +} // namespace + +int main(int argc, char *argv[]) { + try { + uintptr_t address = 0; + size_t size = 0; + bool clearBefore = false; + bool clearAfter = false; + + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + + if (arg == "--address") { + address = std::stol(argv[++i], nullptr, 0); + } else if (arg == "-c") { + clearAfter = true; + } else if (arg == "-C") { + clearBefore = true; + } else if (arg == "-h" || arg == "--help") { + help(argv[0]); + ::exit(0); + } else { + std::cerr << "Illegal argument '" + arg + "'" << std::endl; + help(argv[0]); + ::exit(1); + } + } + + if (address == 0) { + getAddressSizeFromDtb(address, size); + } + + EthosU::DevMem::Log log(address, size); + + if (clearBefore) { + log.clear(); + } + + log.print(); + + if (clearAfter) { + log.clear(); + } + } catch (std::exception &e) { + printf("Error: %s\n", e.what()); + return 1; + } + + return 0; +} -- cgit v1.2.1