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
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 <cstddef>
+#include <iostream>
+
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+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<char *>(
+        ::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 <exception>
+#include <string>
+
+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 <typename T>
+    void read(T &dst, size_t offset) {
+        read(reinterpret_cast<char *>(&dst), sizeof(dst), offset);
+    }
+
+    void write(char *src, size_t length, size_t offset);
+
+    template <typename T>
+    void write(T &src, size_t offset) {
+        write(reinterpret_cast<char *>(&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 <algorithm>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <dirent.h>
+
+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<Path> find(const std::string &name) const {
+        std::vector<Path> 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<Path> &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<char>(ifs), std::istreambuf_iterator<char>());
+    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<char> getProperty(const std::string &name) const {
+        const Path propertyPath = path / name;
+        std::ifstream ifs(propertyPath.c_str(), std::ios_base::binary);
+        std::vector<char> content =
+            std::vector<char>(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
+        return content;
+    }
+
+    std::vector<std::string> getString(const std::string &name) const {
+        std::vector<char> property = getProperty(name);
+        std::vector<std::string> names;
+
+        for (std::vector<char>::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<char> property = getProperty(name);
+
+        switch (size) {
+        case 1:
+            return toCpu<uint8_t>(&property[offset]);
+        case 2:
+            return toCpu<uint16_t>(&property[offset]);
+        case 4:
+            return toCpu<uint32_t>(&property[offset]);
+        case 8:
+            return toCpu<uint64_t>(&property[offset]);
+        default:
+            throw EthosU::DevMem::Exception("Illegal integer size");
+        }
+    }
+
+    static constexpr bool isBigEndian() {
+        int d = 0x12345678;
+        return (d & 0xf) == 1;
+    }
+
+    template <typename T>
+    static T toCpu(const char *src) {
+        T dst;
+        char *d = reinterpret_cast<char *>(&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 <path>/**/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;
+}