pipedal_kconfig (Ubuntu Pi/Uboot)
This commit is contained in:
Vendored
+3
-1
@@ -21,7 +21,9 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
// Resolved by CMake Tools:
|
// Resolved by CMake Tools:
|
||||||
"program": "${command:cmake.launchTargetPath}",
|
"program": "${command:cmake.launchTargetPath}",
|
||||||
"args": [ ],
|
"args": [
|
||||||
|
//"-h"
|
||||||
|
],
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"environment": [
|
"environment": [
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ set (CMAKE_INSTALL_PREFIX "/usr/")
|
|||||||
include(CTest)
|
include(CTest)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#add_subdirectory("submodules/pipedal_p2pd")
|
#add_subdirectory("submodules/pipedal_p2pd")
|
||||||
|
|
||||||
add_subdirectory("PiPedalCommon")
|
add_subdirectory("PiPedalCommon")
|
||||||
|
|||||||
@@ -306,3 +306,37 @@ SysExecOutput pipedal::sysExecForOutput(const std::string &program, const std::s
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int pipedal::SudoExec(int argc, char **argv)
|
||||||
|
{
|
||||||
|
// re-execute with SUDO in order to prompt for SUDO credentials once only.
|
||||||
|
std::vector<char *> args;
|
||||||
|
std::string pkexec = "/usr/bin/sudo"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
|
||||||
|
args.push_back((char *)(pkexec.c_str()));
|
||||||
|
|
||||||
|
std::string sPath = getSelfExePath();
|
||||||
|
args.push_back(const_cast<char *>(sPath.c_str()));
|
||||||
|
for (int arg = 1; arg < argc; ++arg)
|
||||||
|
{
|
||||||
|
args.push_back(const_cast<char *>(argv[arg]));
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push_back(nullptr);
|
||||||
|
|
||||||
|
char **newArgs = &args[0];
|
||||||
|
|
||||||
|
int pbPid;
|
||||||
|
int returnValue = 0;
|
||||||
|
|
||||||
|
if ((pbPid = fork()) == 0)
|
||||||
|
{
|
||||||
|
return execv(newArgs[0], newArgs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
waitpid(pbPid, &returnValue, 0);
|
||||||
|
int exitStatus = WEXITSTATUS(returnValue);
|
||||||
|
return exitStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,5 +43,9 @@ namespace pipedal
|
|||||||
void sysExecTerminate(ProcessId pid_, int termTimeoutMs = 1000, int killTimeoutMs = 500); // returns the process's exit status.
|
void sysExecTerminate(ProcessId pid_, int termTimeoutMs = 1000, int killTimeoutMs = 500); // returns the process's exit status.
|
||||||
int sysExecWait(ProcessId pid);
|
int sysExecWait(ProcessId pid);
|
||||||
|
|
||||||
|
// re-execute the current program with current argument, but with sudo.
|
||||||
|
int SudoExec(int argc, char **argv);
|
||||||
|
|
||||||
|
|
||||||
std::string getSelfExePath();
|
std::string getSelfExePath();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
// Copyright (c) 2024 Robin E. R. Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#include "BootConfig.hpp"
|
||||||
|
#include <filesystem>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include "ss.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include "SysExec.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
#include <set>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
|
||||||
|
static bool IsGrubBootLoader()
|
||||||
|
{
|
||||||
|
const std::vector<std::string> grub_config_paths = {
|
||||||
|
"/boot/grub/grub.cfg",
|
||||||
|
"/boot/grub2/grub.cfg",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &path : grub_config_paths)
|
||||||
|
{
|
||||||
|
if (std::filesystem::exists(path))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::set<std::string> KNOWN_KERNEL_TYPES = {
|
||||||
|
"PREEMPT_NONE",
|
||||||
|
"PREEMPT_VOLUNTARY",
|
||||||
|
"PREEMPT",
|
||||||
|
"PREEMPT_RT",
|
||||||
|
"PREEMPT_DYNAMIC"};
|
||||||
|
|
||||||
|
std::string GetKernelType()
|
||||||
|
{
|
||||||
|
auto result = sysExecForOutput("uname", "-a");
|
||||||
|
if (result.exitCode != EXIT_SUCCESS)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unable to execute 'uname -a'");
|
||||||
|
}
|
||||||
|
std::vector<std::string> items = split(result.output, ' ');
|
||||||
|
for (std::string &item : items)
|
||||||
|
{
|
||||||
|
if (KNOWN_KERNEL_TYPES.contains(item))
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ReadFileLine(const fs::path &path)
|
||||||
|
{
|
||||||
|
if (!fs::exists(path))
|
||||||
|
{
|
||||||
|
throw std::runtime_error(SS("File does not exist: " << path ));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream f(path);
|
||||||
|
if (!f.is_open())
|
||||||
|
{
|
||||||
|
throw std::runtime_error(SS("Can't read file " << path));
|
||||||
|
}
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
std::getline(f, line);
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
std::vector<std::string> ReadFileArgs(const fs::path&path)
|
||||||
|
{
|
||||||
|
std::string line = ReadFileLine(path);
|
||||||
|
|
||||||
|
return split(line,' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
BootConfig::BootConfig()
|
||||||
|
{
|
||||||
|
if (fs::exists("/boot/firmware/cmdline.txt"))
|
||||||
|
{
|
||||||
|
this->bootLoader = BootLoaderT::UBoot;
|
||||||
|
}
|
||||||
|
else if (IsGrubBootLoader())
|
||||||
|
{
|
||||||
|
this->bootLoader = BootLoaderT::Grub;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->bootLoader = BootLoaderT::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->kernelType = GetKernelType();
|
||||||
|
|
||||||
|
if (this->kernelType == "PREEMPT_DYNAMIC")
|
||||||
|
{
|
||||||
|
this->dynamicScheduler = DynamicSchedulerT::Voluntary; // the default.
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cmdLine = ReadFileLine("/proc/cmdline");
|
||||||
|
std::vector<std::string> cmdLineArgs = split(cmdLine, ' ');
|
||||||
|
|
||||||
|
for (const auto &arg : cmdLineArgs)
|
||||||
|
{
|
||||||
|
if (arg == "preempt=full")
|
||||||
|
{
|
||||||
|
this->dynamicScheduler = DynamicSchedulerT::Full;
|
||||||
|
}
|
||||||
|
else if (arg == "preempt=voluntary")
|
||||||
|
{
|
||||||
|
this->dynamicScheduler = DynamicSchedulerT::Voluntary;
|
||||||
|
}
|
||||||
|
else if (arg == "preempt=none")
|
||||||
|
{
|
||||||
|
this->dynamicScheduler = DynamicSchedulerT::None;
|
||||||
|
}
|
||||||
|
else if (arg.starts_with("preempt="))
|
||||||
|
{
|
||||||
|
this->dynamicScheduler = DynamicSchedulerT::Unknown;
|
||||||
|
}
|
||||||
|
else if (arg == "threadirqs")
|
||||||
|
{
|
||||||
|
this->threadedIrqs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->canSetThreadIrqs = this->kernelType == "PREEMPT" || this->kernelType == "PREEMPT_DYNAMIC";
|
||||||
|
}
|
||||||
|
|
||||||
|
BootConfig::~BootConfig()
|
||||||
|
{
|
||||||
|
this->thread = nullptr; // join and clean up the tread before deleting ANYTHING else.
|
||||||
|
}
|
||||||
|
bool BootConfig::operator==(BootConfig &other) const
|
||||||
|
{
|
||||||
|
return this->bootLoader == other.bootLoader && this->kernelType == other.kernelType && this->preemptMode == other.preemptMode && this->threadedIrqs == other.threadedIrqs;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BootConfig::CanWriteConfig()
|
||||||
|
{
|
||||||
|
if (!CanSetThreadIrqs() && dynamicScheduler == DynamicSchedulerT::NotApplicable)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (this->bootLoader != BootLoaderT::Unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BootConfig::WriteConfiguration(std::function<void(bool success, std::string errorMessage)> onComplete)
|
||||||
|
{
|
||||||
|
this->thread = nullptr; // wait for any previous operations to complete.
|
||||||
|
if (!changed)
|
||||||
|
{
|
||||||
|
if (onComplete) onComplete(true, "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
if (this->bootLoader == BootLoaderT::UBoot)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
WriteUBootConfiguration(onComplete);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
if (onComplete) onComplete(false, e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this->bootLoader == BootLoaderT::Grub)
|
||||||
|
{
|
||||||
|
WriteGrubConfiguration(onComplete);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (onComplete) onComplete(false, "Unsupported bootloader.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string commandLineOption(BootConfig::DynamicSchedulerT dynamicScheduler)
|
||||||
|
{
|
||||||
|
switch (dynamicScheduler)
|
||||||
|
{
|
||||||
|
case BootConfig::DynamicSchedulerT::None:
|
||||||
|
return "preempt=none";
|
||||||
|
case BootConfig::DynamicSchedulerT::Voluntary:
|
||||||
|
return "preempt=voluntary";
|
||||||
|
case BootConfig::DynamicSchedulerT::Full:
|
||||||
|
return "preempt=full";
|
||||||
|
default:
|
||||||
|
throw std::range_error("Invalid value.");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void BootConfig::WriteUBootConfiguration(std::function<void(bool success, std::string errorMessage)> onComplete)
|
||||||
|
{
|
||||||
|
this->thread = nullptr; // blocks if already running.
|
||||||
|
|
||||||
|
this->thread = std::make_unique<std::jthread>(
|
||||||
|
[this, onComplete]()
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fs::path cmdlineFile = "/boot/firmware/cmdline.txt";
|
||||||
|
std::vector<std::string> bootArgs = ReadFileArgs(cmdlineFile);
|
||||||
|
|
||||||
|
std::vector<std::string> newBootArgs;
|
||||||
|
for (const auto&value: bootArgs)
|
||||||
|
{
|
||||||
|
if (value != "threadirqs" && !value.starts_with("preempt="))
|
||||||
|
{
|
||||||
|
newBootArgs.push_back(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->canSetThreadIrqs && this->threadedIrqs)
|
||||||
|
{
|
||||||
|
newBootArgs.push_back("threadirqs");
|
||||||
|
}
|
||||||
|
if (this->dynamicScheduler != DynamicSchedulerT::NotApplicable)
|
||||||
|
{
|
||||||
|
newBootArgs.push_back(commandLineOption(this->dynamicScheduler));
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
bool firstArg = true;
|
||||||
|
for (const std::string&arg: newBootArgs) {
|
||||||
|
if (!firstArg) {
|
||||||
|
ss << ' ';
|
||||||
|
}
|
||||||
|
firstArg = false;
|
||||||
|
ss << arg;
|
||||||
|
}
|
||||||
|
ss << '\n';
|
||||||
|
|
||||||
|
std::string newCmdLine = ss.str();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ofstream f(cmdlineFile);
|
||||||
|
if (!f.is_open())
|
||||||
|
{
|
||||||
|
throw std::runtime_error(SS("Can't write to " << cmdlineFile));
|
||||||
|
}
|
||||||
|
f << newCmdLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onComplete)
|
||||||
|
{
|
||||||
|
onComplete(true, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
if (onComplete)
|
||||||
|
{
|
||||||
|
onComplete(false, e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void BootConfig::WriteGrubConfiguration(std::function<void(bool success, std::string errorMessage)> onComplete)
|
||||||
|
{
|
||||||
|
this->thread = nullptr; // blocks if already running.
|
||||||
|
|
||||||
|
this->thread = std::make_unique<std::jthread>(
|
||||||
|
[this, onComplete]()
|
||||||
|
{
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
if (onComplete)
|
||||||
|
{
|
||||||
|
onComplete(false, "Grub handler not implemeneted.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void BootConfig::DynamicScheduler(DynamicSchedulerT value)
|
||||||
|
{
|
||||||
|
if (value != this->dynamicScheduler)
|
||||||
|
{
|
||||||
|
if (this->dynamicScheduler == DynamicSchedulerT::NotApplicable)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Illegal operation.");
|
||||||
|
}
|
||||||
|
this->dynamicScheduler = value;
|
||||||
|
this->changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void BootConfig::ThreadedIrqs(bool value)
|
||||||
|
{
|
||||||
|
if (value != threadedIrqs)
|
||||||
|
{
|
||||||
|
if (!CanSetThreadIrqs())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Illegal operation.");
|
||||||
|
}
|
||||||
|
this->threadedIrqs = value;
|
||||||
|
this->changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2024 Robin E. R. Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <thread>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace pipedal {
|
||||||
|
class BootConfig {
|
||||||
|
public:
|
||||||
|
BootConfig();
|
||||||
|
~BootConfig();
|
||||||
|
|
||||||
|
enum class BootLoaderT {
|
||||||
|
UBoot,
|
||||||
|
Grub,
|
||||||
|
Unknown
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DynamicSchedulerT {
|
||||||
|
None = 0,
|
||||||
|
Voluntary = 1,
|
||||||
|
Full = 2,
|
||||||
|
|
||||||
|
NotApplicable = -1,
|
||||||
|
Unknown = -2,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// BEWARE: The onComplete function gets called from off the main thread on machines with GRUB bootloaders!
|
||||||
|
void WriteConfiguration(std::function<void(bool success,std::string errorMessage)> onComplete);
|
||||||
|
BootLoaderT BootLoader() const { return bootLoader; }
|
||||||
|
const std::string&KernelType() const { return kernelType; }
|
||||||
|
|
||||||
|
bool CanSetThreadIrqs() const { return canSetThreadIrqs; }
|
||||||
|
bool ThreadedIrqs() const { return threadedIrqs; }
|
||||||
|
void ThreadedIrqs(bool value);
|
||||||
|
|
||||||
|
const DynamicSchedulerT DynamicScheduler() const { return dynamicScheduler; }
|
||||||
|
void DynamicScheduler(DynamicSchedulerT value);
|
||||||
|
|
||||||
|
|
||||||
|
bool CanWriteConfig();
|
||||||
|
|
||||||
|
bool Changed() const { return changed; }
|
||||||
|
|
||||||
|
bool operator==(BootConfig&other) const;
|
||||||
|
private:
|
||||||
|
std::unique_ptr<std::jthread> thread;
|
||||||
|
|
||||||
|
void WriteUBootConfiguration(std::function<void(bool success,std::string errorMessage)> onComplete);
|
||||||
|
void WriteGrubConfiguration(std::function<void(bool success,std::string errorMessage)> onComplete);
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
BootLoaderT bootLoader = BootLoaderT::Unknown;
|
||||||
|
std::string kernelType;
|
||||||
|
std::string preemptMode;
|
||||||
|
DynamicSchedulerT dynamicScheduler = DynamicSchedulerT::NotApplicable;
|
||||||
|
bool canSetThreadIrqs = false;
|
||||||
|
bool threadedIrqs = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -11,6 +11,29 @@ set (USE_SANITIZE OFF) # seems to be broken on Ubuntu 24.10
|
|||||||
|
|
||||||
set(CXX_STANDARD 20)
|
set(CXX_STANDARD 20)
|
||||||
|
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
cmake_policy(SET CMP0169 OLD)
|
||||||
|
|
||||||
|
set(FETCHCONTENT_QUIET ON)
|
||||||
|
set(FTXUI_QUIET ON)
|
||||||
|
|
||||||
|
FetchContent_Declare(ftxui
|
||||||
|
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
||||||
|
GIT_TAG v5.0.0
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_GetProperties(ftxui)
|
||||||
|
if(NOT ftxui_POPULATED)
|
||||||
|
FetchContent_Populate(ftxui)
|
||||||
|
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "*** ${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
include(FindPkgConfig)
|
include(FindPkgConfig)
|
||||||
|
|
||||||
find_package(sdbus-c++ REQUIRED)
|
find_package(sdbus-c++ REQUIRED)
|
||||||
@@ -303,6 +326,21 @@ if(${USE_PCH})
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
#################################
|
||||||
|
|
||||||
|
add_executable(pipedal_kconfig
|
||||||
|
kconfigMain.cpp
|
||||||
|
BootConfig.cpp BootConfig.hpp
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(pipedal_kconfig
|
||||||
|
PRIVATE ftxui::screen
|
||||||
|
PRIVATE ftxui::dom
|
||||||
|
PRIVATE ftxui::component # Not needed for this example.
|
||||||
|
PRIVATE PiPedalCommon
|
||||||
|
)
|
||||||
|
|
||||||
#################################
|
#################################
|
||||||
add_executable(pipedald
|
add_executable(pipedald
|
||||||
asan_options.cpp # disable leak checking for sanitize=address.
|
asan_options.cpp # disable leak checking for sanitize=address.
|
||||||
|
|||||||
@@ -561,38 +561,6 @@ void InstallAudioService()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int SudoExec(int argc, char **argv)
|
|
||||||
{
|
|
||||||
// re-execute with SUDO in order to prompt for SUDO credentials once only.
|
|
||||||
std::vector<char *> args;
|
|
||||||
std::string pkexec = "/usr/bin/sudo"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
|
|
||||||
args.push_back((char *)(pkexec.c_str()));
|
|
||||||
|
|
||||||
std::string sPath = getSelfExePath();
|
|
||||||
args.push_back(const_cast<char *>(sPath.c_str()));
|
|
||||||
for (int arg = 1; arg < argc; ++arg)
|
|
||||||
{
|
|
||||||
args.push_back(const_cast<char *>(argv[arg]));
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push_back(nullptr);
|
|
||||||
|
|
||||||
char **newArgs = &args[0];
|
|
||||||
|
|
||||||
int pbPid;
|
|
||||||
int returnValue = 0;
|
|
||||||
|
|
||||||
if ((pbPid = fork()) == 0)
|
|
||||||
{
|
|
||||||
return execv(newArgs[0], newArgs);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
waitpid(pbPid, &returnValue, 0);
|
|
||||||
int exitStatus = WEXITSTATUS(returnValue);
|
|
||||||
return exitStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsP2pServiceEnabled()
|
static bool IsP2pServiceEnabled()
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Robin Davies
|
// Copyright (c) 2024 Robin E. R. Davies
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
// this software and associated documentation files (the "Software"), to deal in
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -183,7 +183,12 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
++startOfOverflow;
|
++startOfOverflow;
|
||||||
}
|
}
|
||||||
std::string overflow = string(&lineBuffer[startOfOverflow], lineBuffer.size() - startOfOverflow);
|
|
||||||
|
std::string overflow;
|
||||||
|
size_t extra = lineBuffer.size() - startOfOverflow;
|
||||||
|
if (extra) {
|
||||||
|
overflow = std::string(&lineBuffer[startOfOverflow], extra);
|
||||||
|
}
|
||||||
lineBuffer.resize(breakPos);
|
lineBuffer.resize(breakPos);
|
||||||
|
|
||||||
WriteLine();
|
WriteLine();
|
||||||
|
|||||||
@@ -0,0 +1,710 @@
|
|||||||
|
// Copyright (c) 2024 Robin E. R. Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Preamble:
|
||||||
|
|
||||||
|
The choice of backend for this project was unfortunate. The ftxui project has ungodly horrible problems
|
||||||
|
dealing with mouse and keyboard focus. Really embarassingly horrible problems. And no real sensible
|
||||||
|
support for modal dialogs. As well as deficiences in keyboard handling (no way to intercept the ESC key, for example,
|
||||||
|
which would have been a nice feature).
|
||||||
|
|
||||||
|
So the code that follows is .... unpleasant, as it does its best to work around severe problesm and
|
||||||
|
deficiencies in FTXUI.
|
||||||
|
|
||||||
|
Tempting as it is to rewrite from scratch, FTXUI *probably* deals with unpleasant platform issues and
|
||||||
|
complications in ncurses, which may not be entirely straighforward on windows. The tab/nav/cursor implementation
|
||||||
|
is also *almost* nice (although being able to use the enter key for OK in dialogs would have been nice, and ESC for
|
||||||
|
cancel).
|
||||||
|
|
||||||
|
Things to watch out for: Focus handling in the FTXUI RadioBox control was unsalvageable. This code hacks together
|
||||||
|
a replacement implementation using Checkboxes instead. Checkbox focus handling was also broken, but is (just barely)
|
||||||
|
salageable. Also broken under window resizing. (It recovers when you mose over).
|
||||||
|
|
||||||
|
Nevertheless, FTXUI is truly awful. Do not use this package. You have been warned.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#undef _GLIBCXX_DEBUG
|
||||||
|
|
||||||
|
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||||
|
#include "ftxui/component/component.hpp" // for Input, Renderer, Vertical
|
||||||
|
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||||
|
#include "ftxui/component/component_options.hpp" // for InputOption
|
||||||
|
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
|
||||||
|
#include "ftxui/dom/elements.hpp" // for text, hbox, separator, Element, operator|, vbox, border
|
||||||
|
#include "ftxui/util/ref.hpp" // for Ref
|
||||||
|
#include "ftxui/component/animation.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include "BootConfig.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include "CommandLineParser.hpp"
|
||||||
|
#include "PrettyPrinter.hpp"
|
||||||
|
#include "SysExec.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace ftxui;
|
||||||
|
using Clock = ftxui::animation::Clock;
|
||||||
|
using namespace pipedal;
|
||||||
|
|
||||||
|
std::string spinnerText(const Clock::time_point &startTime)
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
constexpr int MAX_LENGTH = 40;
|
||||||
|
constexpr int N_DOTS = 3;
|
||||||
|
|
||||||
|
int64_t ms = std::chrono::duration_cast<milliseconds>(Clock::now() - startTime).count();
|
||||||
|
|
||||||
|
int64_t position = (ms * MAX_LENGTH / 3000) % (MAX_LENGTH + N_DOTS);
|
||||||
|
|
||||||
|
char result[MAX_LENGTH - 1];
|
||||||
|
for (int64_t i = 0; i < MAX_LENGTH; ++i)
|
||||||
|
{
|
||||||
|
if (i >= position && i < position + N_DOTS)
|
||||||
|
{
|
||||||
|
result[i] = '.';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result[i] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[MAX_LENGTH] = '\0';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
RadioboxOption CustomRadioboxOption(int *value, int *forceFocus)
|
||||||
|
{
|
||||||
|
RadioboxOption option = RadioboxOption::Simple();
|
||||||
|
if (*forceFocus >= 0)
|
||||||
|
{
|
||||||
|
// option.focused_entry = *forceFocus;
|
||||||
|
*forceFocus = -1;
|
||||||
|
}
|
||||||
|
option.on_change = [value, forceFocus]() mutable
|
||||||
|
{
|
||||||
|
*forceFocus = *value;
|
||||||
|
};
|
||||||
|
|
||||||
|
option.transform = [](const EntryState &s) -> Element
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
// Microsoft terminal do not use fonts able to render properly the default
|
||||||
|
// radiobox glyph.
|
||||||
|
auto prefix = text(s.state ? "(*) " : "( ) "); // NOLINT
|
||||||
|
#else
|
||||||
|
auto prefix = text(s.state ? "◉ " : "○ "); // NOLINT
|
||||||
|
#endif
|
||||||
|
auto t = text(s.label);
|
||||||
|
if (s.active != s.focused)
|
||||||
|
{
|
||||||
|
s.label;
|
||||||
|
}
|
||||||
|
if (s.active)
|
||||||
|
{
|
||||||
|
t = t | color(Color::Red);
|
||||||
|
}
|
||||||
|
if (s.focused)
|
||||||
|
{
|
||||||
|
t = t | bgcolor(Color::GrayDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hbox({prefix, t});
|
||||||
|
};
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
CheckboxOption CustomCheckboxOption(bool *forceFocus)
|
||||||
|
{
|
||||||
|
CheckboxOption option = CheckboxOption::Simple();
|
||||||
|
|
||||||
|
option.on_change = [forceFocus]() mutable
|
||||||
|
{
|
||||||
|
*forceFocus = true;
|
||||||
|
};
|
||||||
|
option.transform = [](const EntryState &s) -> Element
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
// Microsoft terminal do not use fonts able to render properly the default
|
||||||
|
// radiobox glyph.
|
||||||
|
auto prefix = text(s.state ? "[X] " : "[ ] "); // NOLINT
|
||||||
|
#else
|
||||||
|
auto prefix = text(s.state ? "▣ " : "☐ "); // NOLINT
|
||||||
|
#endif
|
||||||
|
auto t = text(s.label);
|
||||||
|
if (s.active != s.focused)
|
||||||
|
{
|
||||||
|
s.label;
|
||||||
|
}
|
||||||
|
if (s.active)
|
||||||
|
{
|
||||||
|
t = t | color(Color::White);
|
||||||
|
}
|
||||||
|
if (s.focused)
|
||||||
|
{
|
||||||
|
t = t | color(Color::White) | bgcolor(Color::GrayDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hbox({prefix, t});
|
||||||
|
};
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
CheckboxOption CustomRadioCheckboxOption(bool *selections, size_t size, size_t index, int *forceFocus)
|
||||||
|
{
|
||||||
|
CheckboxOption option = CheckboxOption::Simple();
|
||||||
|
|
||||||
|
option.on_change = [selections, size, index, forceFocus]() mutable
|
||||||
|
{
|
||||||
|
*forceFocus = index;
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
selections[i] = (i == index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
option.transform = [](const EntryState &s) -> Element
|
||||||
|
{
|
||||||
|
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
||||||
|
// Microsoft terminal do not use fonts able to render properly the default
|
||||||
|
// radiobox glyph.
|
||||||
|
auto prefix = text(s.state ? "(*) " : "( ) "); // NOLINT
|
||||||
|
#else
|
||||||
|
auto prefix = text(s.state ? "◉ " : "○ "); // NOLINT
|
||||||
|
#endif
|
||||||
|
auto t = text(s.label);
|
||||||
|
if (s.active != s.focused)
|
||||||
|
{
|
||||||
|
s.label;
|
||||||
|
}
|
||||||
|
if (s.active)
|
||||||
|
{
|
||||||
|
t = t | color(Color::White);
|
||||||
|
}
|
||||||
|
if (s.focused)
|
||||||
|
{
|
||||||
|
t = t | color(Color::White) | bgcolor(Color::GrayDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hbox({prefix, t});
|
||||||
|
};
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<std::jthread> setBootParametersThread;
|
||||||
|
|
||||||
|
void setBootParameters(std::shared_ptr<BootConfig> newConfig, std::function<void(bool success, std::string errorMessage)> onComplete)
|
||||||
|
{
|
||||||
|
newConfig->WriteConfiguration(onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
void kconfigUi(void)
|
||||||
|
{
|
||||||
|
const std::string kernelType = "PREEMPT_DYNAMIC";
|
||||||
|
|
||||||
|
int kernelMode = 0;
|
||||||
|
|
||||||
|
const int NO_DIALOG = 0;
|
||||||
|
const int PROCESSING_DIALOG = 1;
|
||||||
|
const int REBOOT_DIALOG = 2;
|
||||||
|
const int ERROR_DIALOG = 3;
|
||||||
|
|
||||||
|
Clock::time_point startTime = Clock::now();
|
||||||
|
|
||||||
|
int dialogIndex = 0;
|
||||||
|
std::string errorMessage;
|
||||||
|
|
||||||
|
auto screen = ScreenInteractive::Fullscreen();
|
||||||
|
|
||||||
|
auto quit = [&screen]()
|
||||||
|
{
|
||||||
|
screen.Exit();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex updateCallbackMutex;
|
||||||
|
bool updatePending = false;
|
||||||
|
bool updateComplete = false;
|
||||||
|
std::string updateErrorMessage;
|
||||||
|
|
||||||
|
auto onError = [&errorMessage, &dialogIndex](const std::string &error) mutable
|
||||||
|
{
|
||||||
|
errorMessage = error;
|
||||||
|
dialogIndex = ERROR_DIALOG;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool useThreadedIrqs = true;
|
||||||
|
|
||||||
|
BootConfig::DynamicSchedulerT dynamicScheduler = BootConfig::DynamicSchedulerT::NotApplicable;
|
||||||
|
bool threadedIrqsAvailable = false;
|
||||||
|
bool configRequired = false;
|
||||||
|
|
||||||
|
std::unique_ptr<BootConfig> bootConfig;
|
||||||
|
using DynamicSchedulerT = BootConfig::DynamicSchedulerT;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bootConfig = std::make_unique<BootConfig>();
|
||||||
|
threadedIrqsAvailable = bootConfig->CanSetThreadIrqs();
|
||||||
|
dynamicScheduler = bootConfig->DynamicScheduler();
|
||||||
|
useThreadedIrqs = bootConfig->ThreadedIrqs();
|
||||||
|
configRequired = bootConfig->DynamicScheduler() != DynamicSchedulerT::NotApplicable || useThreadedIrqs;
|
||||||
|
|
||||||
|
if (!bootConfig->CanWriteConfig())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unrecognized boot loader. Unable to reconfigure the kernel.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
onError(e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool forceThreadedIrqFocus = false;
|
||||||
|
|
||||||
|
|
||||||
|
int dynamicSchedulerIndex = (int)dynamicScheduler;
|
||||||
|
std::vector<ConstStringRef> preemptModeLabelRefs = {
|
||||||
|
" none ",
|
||||||
|
" voluntary ",
|
||||||
|
" full (recommended) "};
|
||||||
|
bool preemptModeSelections[3]; // std::vector<bool> is bizarrely specialized, so it doesn't wrok.
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
preemptModeSelections[i] = (i == dynamicSchedulerIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int forceSchedulerFocus = -1;
|
||||||
|
|
||||||
|
Component preemptCheckBox0 =
|
||||||
|
Checkbox(
|
||||||
|
preemptModeLabelRefs[0],
|
||||||
|
&(preemptModeSelections[0]),
|
||||||
|
CustomRadioCheckboxOption(preemptModeSelections, 3, 0, &forceSchedulerFocus));
|
||||||
|
Component preemptCheckBox1 =
|
||||||
|
Checkbox(
|
||||||
|
preemptModeLabelRefs[1],
|
||||||
|
&(preemptModeSelections[1]),
|
||||||
|
CustomRadioCheckboxOption(preemptModeSelections, 3, 1, &forceSchedulerFocus));
|
||||||
|
Component preemptCheckBox2 =
|
||||||
|
Checkbox(
|
||||||
|
preemptModeLabelRefs[2],
|
||||||
|
&(preemptModeSelections[2]),
|
||||||
|
CustomRadioCheckboxOption(preemptModeSelections, 3, 2, &forceSchedulerFocus));
|
||||||
|
|
||||||
|
std::vector<Component> preemptCheckboxes{
|
||||||
|
preemptCheckBox0,
|
||||||
|
preemptCheckBox1,
|
||||||
|
preemptCheckBox2,
|
||||||
|
};
|
||||||
|
|
||||||
|
ConstStringRef checkboxRef{" Enable threadirqs (recommended) "};
|
||||||
|
Component threadedIrqCheckbox = Checkbox(checkboxRef, &useThreadedIrqs, CustomCheckboxOption(&forceThreadedIrqFocus));
|
||||||
|
|
||||||
|
// The basic input components:
|
||||||
|
auto handleSave = [&]()
|
||||||
|
{
|
||||||
|
if (updatePending)
|
||||||
|
{
|
||||||
|
onError("Save already running.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogIndex = PROCESSING_DIALOG;
|
||||||
|
|
||||||
|
std::shared_ptr<BootConfig> newBootConfig = std::make_shared<BootConfig>();
|
||||||
|
|
||||||
|
if (bootConfig->CanSetThreadIrqs())
|
||||||
|
{
|
||||||
|
newBootConfig->ThreadedIrqs(useThreadedIrqs);
|
||||||
|
}
|
||||||
|
if (bootConfig->DynamicScheduler() != DynamicSchedulerT::NotApplicable)
|
||||||
|
{
|
||||||
|
int schedulerIndex = -1;
|
||||||
|
for (size_t i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
if (preemptModeSelections[i])
|
||||||
|
{
|
||||||
|
schedulerIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (schedulerIndex == -1)
|
||||||
|
{
|
||||||
|
onError("Internal state error.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newBootConfig->DynamicScheduler((DynamicSchedulerT)schedulerIndex);
|
||||||
|
|
||||||
|
if (newBootConfig->Changed())
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard lock{updateCallbackMutex};
|
||||||
|
updatePending = true;
|
||||||
|
updateComplete = false;
|
||||||
|
updateErrorMessage = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
newBootConfig->WriteConfiguration(
|
||||||
|
[&](bool success, std::string errorMessage)
|
||||||
|
{
|
||||||
|
// potentially wrong-threaded. We pick up the result because we're animating the "..."
|
||||||
|
std::lock_guard lock(updateCallbackMutex);
|
||||||
|
updateComplete = true;
|
||||||
|
updateErrorMessage = errorMessage;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// no changes. Just quit.
|
||||||
|
quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Component okButton = Button(
|
||||||
|
ConstStringRef(" OK "),
|
||||||
|
[&]() mutable
|
||||||
|
{
|
||||||
|
screen.ExitLoopClosure()();
|
||||||
|
},
|
||||||
|
ButtonOption::Ascii());
|
||||||
|
|
||||||
|
Component saveButton = Button(
|
||||||
|
ConstStringRef(" Save "),
|
||||||
|
[&]() mutable
|
||||||
|
{
|
||||||
|
handleSave();
|
||||||
|
},
|
||||||
|
ButtonOption::Ascii());
|
||||||
|
|
||||||
|
Component cancelButton = Button(
|
||||||
|
ConstStringRef(" Cancel "),
|
||||||
|
[&screen]() mutable
|
||||||
|
{
|
||||||
|
screen.ExitLoopClosure()();
|
||||||
|
},
|
||||||
|
ButtonOption::Ascii());
|
||||||
|
|
||||||
|
// The component tree:
|
||||||
|
Component tabOrder;
|
||||||
|
|
||||||
|
if (configRequired)
|
||||||
|
{
|
||||||
|
Components components;
|
||||||
|
if (dynamicScheduler != DynamicSchedulerT::NotApplicable)
|
||||||
|
{
|
||||||
|
components.push_back(preemptCheckBox0);
|
||||||
|
components.push_back(preemptCheckBox1);
|
||||||
|
components.push_back(preemptCheckBox2);
|
||||||
|
}
|
||||||
|
if (threadedIrqsAvailable)
|
||||||
|
{
|
||||||
|
components.push_back(threadedIrqCheckbox);
|
||||||
|
}
|
||||||
|
components.push_back(Container::Horizontal({cancelButton, saveButton}));
|
||||||
|
|
||||||
|
tabOrder = Container::Vertical(components);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tabOrder = Container::Vertical({okButton});
|
||||||
|
}
|
||||||
|
|
||||||
|
// component tree for Processing dialog.
|
||||||
|
auto focusTrap = Container::Horizontal({Button(ButtonOption::Ascii())});
|
||||||
|
|
||||||
|
auto mainRenderer = Renderer(
|
||||||
|
tabOrder,
|
||||||
|
[&]() mutable
|
||||||
|
{
|
||||||
|
if (forceThreadedIrqFocus)
|
||||||
|
{
|
||||||
|
threadedIrqCheckbox->TakeFocus();
|
||||||
|
forceThreadedIrqFocus = false;
|
||||||
|
}
|
||||||
|
if (forceSchedulerFocus >= 0)
|
||||||
|
{
|
||||||
|
preemptCheckboxes[forceSchedulerFocus]->TakeFocus();
|
||||||
|
forceSchedulerFocus = -1;
|
||||||
|
}
|
||||||
|
Elements topLevelElements;
|
||||||
|
|
||||||
|
Elements contentElements;
|
||||||
|
{
|
||||||
|
contentElements.push_back(hbox(text("Kernel type: "), text(kernelType)));
|
||||||
|
contentElements.push_back(text(""));
|
||||||
|
|
||||||
|
if (!configRequired)
|
||||||
|
{
|
||||||
|
contentElements.push_back(text("No configuration required."));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Elements kernelCommandLineOptions;
|
||||||
|
{
|
||||||
|
kernelCommandLineOptions.push_back(text(""));
|
||||||
|
if (dynamicScheduler != DynamicSchedulerT::NotApplicable)
|
||||||
|
{
|
||||||
|
kernelCommandLineOptions.push_back(text(" Preempt"));
|
||||||
|
kernelCommandLineOptions.push_back(hbox(text(" "), preemptCheckBox0->Render(), text(" ")));
|
||||||
|
kernelCommandLineOptions.push_back(hbox(text(" "), preemptCheckBox1->Render(), text(" ")));
|
||||||
|
kernelCommandLineOptions.push_back(hbox(text(" "), preemptCheckBox2->Render(), text(" ")));
|
||||||
|
}
|
||||||
|
if (dynamicScheduler != DynamicSchedulerT::NotApplicable && threadedIrqsAvailable)
|
||||||
|
{
|
||||||
|
kernelCommandLineOptions.push_back(text(""));
|
||||||
|
}
|
||||||
|
if (threadedIrqsAvailable)
|
||||||
|
{
|
||||||
|
kernelCommandLineOptions.push_back(hbox(text(" "), threadedIrqCheckbox->Render(), text(" ")));
|
||||||
|
}
|
||||||
|
kernelCommandLineOptions.push_back(text(""));
|
||||||
|
}
|
||||||
|
contentElements.push_back(
|
||||||
|
window(
|
||||||
|
text(" Kernel Commandline Options "),
|
||||||
|
vbox(kernelCommandLineOptions)));
|
||||||
|
}
|
||||||
|
auto dialog = vbox(
|
||||||
|
{text(" PiPedal Kernel Configuration"),
|
||||||
|
separator(),
|
||||||
|
text(""),
|
||||||
|
hbox(text(" "), vbox(contentElements) | color(Color::White), text(" ")),
|
||||||
|
text(""),
|
||||||
|
text(""),
|
||||||
|
configRequired ? hbox(text("") | flex, cancelButton->Render(), saveButton->Render(), text(" "))
|
||||||
|
: hbox(text("") | flex, okButton->Render(), text(" "))}) |
|
||||||
|
(borderStyled(BorderStyle::HEAVY) | color(Color::White));
|
||||||
|
topLevelElements.push_back(std::move(center(dialog)));
|
||||||
|
}
|
||||||
|
return dbox(topLevelElements);
|
||||||
|
});
|
||||||
|
mainRenderer = CatchEvent(
|
||||||
|
mainRenderer,
|
||||||
|
[&](Event event)
|
||||||
|
{
|
||||||
|
if (event.is_character())
|
||||||
|
{
|
||||||
|
if (event == Event::Character('q'))
|
||||||
|
{
|
||||||
|
screen.ExitLoopClosure()();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
auto processDialogRenderer = Renderer(focusTrap, [&]()
|
||||||
|
{
|
||||||
|
auto dialog = vbox(
|
||||||
|
{
|
||||||
|
text(" Applying changes..."),
|
||||||
|
hbox(text(" "),text(spinnerText(startTime)),text(" "))
|
||||||
|
}) | borderHeavy | clear_under | center;
|
||||||
|
|
||||||
|
return dialog; });
|
||||||
|
|
||||||
|
ConstStringRef rebootNowText(" Now ");
|
||||||
|
ConstStringRef rebootLaterText(" Later ");
|
||||||
|
|
||||||
|
auto rebootNowButton = Button(
|
||||||
|
rebootNowText,
|
||||||
|
[&]()
|
||||||
|
{
|
||||||
|
dialogIndex = 0;
|
||||||
|
silentSysExec("sudo reboot now");
|
||||||
|
quit();
|
||||||
|
},
|
||||||
|
ButtonOption::Ascii());
|
||||||
|
auto rebootLaterButton = Button(
|
||||||
|
rebootLaterText,
|
||||||
|
[&]()
|
||||||
|
{
|
||||||
|
dialogIndex = 0;
|
||||||
|
quit();
|
||||||
|
},
|
||||||
|
ButtonOption::Ascii());
|
||||||
|
auto rebootDialogContainer = Container::Horizontal({rebootLaterButton, rebootNowButton});
|
||||||
|
|
||||||
|
auto rebootDialogRenderer = Renderer(
|
||||||
|
rebootDialogContainer,
|
||||||
|
[&]()
|
||||||
|
{
|
||||||
|
return hbox({text(" "),
|
||||||
|
vbox({hbox(text(" "), paragraph("Reboot required. Would you like to reboot now or later?") | size(WIDTH, LESS_THAN, 35), text(" ")),
|
||||||
|
text(""),
|
||||||
|
hbox(text("") | flex, rebootLaterButton->Render(), rebootNowButton->Render(), text(" ")),
|
||||||
|
text(" ")})}) |
|
||||||
|
borderHeavy;
|
||||||
|
});
|
||||||
|
|
||||||
|
////////////// ErrorDialog /////////////////////////////////////////////////
|
||||||
|
|
||||||
|
ConstStringRef errorOkText(" OK ");
|
||||||
|
|
||||||
|
auto errorOkButton = Button(
|
||||||
|
errorOkText,
|
||||||
|
[&]()
|
||||||
|
{
|
||||||
|
dialogIndex = 0;
|
||||||
|
quit();
|
||||||
|
},
|
||||||
|
ButtonOption::Ascii());
|
||||||
|
auto errorDialogContainer = Container::Horizontal({errorOkButton});
|
||||||
|
|
||||||
|
auto errorDialogRenderer = Renderer(
|
||||||
|
errorDialogContainer,
|
||||||
|
[&]()
|
||||||
|
{
|
||||||
|
return vbox(
|
||||||
|
{text(" Error"),
|
||||||
|
separator(),
|
||||||
|
|
||||||
|
vbox({hbox(text(" "), paragraph(errorMessage) | size(WIDTH, LESS_THAN, 35), text(" ")),
|
||||||
|
text(""),
|
||||||
|
hbox(text("") | flex, errorOkButton->Render(), text(" "))
|
||||||
|
|
||||||
|
})}) |
|
||||||
|
borderHeavy;
|
||||||
|
});
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
auto modalDialogContainer = Container::Tab(
|
||||||
|
{
|
||||||
|
|
||||||
|
mainRenderer,
|
||||||
|
processDialogRenderer,
|
||||||
|
rebootDialogRenderer,
|
||||||
|
errorDialogRenderer},
|
||||||
|
&dialogIndex);
|
||||||
|
|
||||||
|
auto modalDialogRenderer = Renderer(
|
||||||
|
modalDialogContainer,
|
||||||
|
[&]() mutable
|
||||||
|
{
|
||||||
|
Element document = modalDialogContainer->ChildAt(0)->Render();
|
||||||
|
if (dialogIndex != 0)
|
||||||
|
{
|
||||||
|
document = dbox(
|
||||||
|
document,
|
||||||
|
modalDialogContainer->ChildAt(dialogIndex)->Render() | clear_under | center);
|
||||||
|
}
|
||||||
|
if (dialogIndex == PROCESSING_DIALOG)
|
||||||
|
{
|
||||||
|
screen.RequestAnimationFrame();
|
||||||
|
{
|
||||||
|
std::lock_guard lock(updateCallbackMutex);
|
||||||
|
if (updatePending && updateComplete)
|
||||||
|
{
|
||||||
|
updatePending = false;
|
||||||
|
updateComplete = false;
|
||||||
|
if (updateErrorMessage.length() != 0)
|
||||||
|
{
|
||||||
|
onError(updateErrorMessage);
|
||||||
|
updateErrorMessage = "";
|
||||||
|
} else {
|
||||||
|
// operation complete.
|
||||||
|
// show the reboot dialog.
|
||||||
|
dialogIndex = REBOOT_DIALOG;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return document;
|
||||||
|
});
|
||||||
|
|
||||||
|
screen.Loop(modalDialogRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void printHelp()
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
PrettyPrinter pp;
|
||||||
|
|
||||||
|
|
||||||
|
pp <<
|
||||||
|
"pipedal_kconfig - Edit kernel commandline arguments. \n"
|
||||||
|
<< "Copyright (c) Robin E. R. Davies"
|
||||||
|
<< "\n\n"
|
||||||
|
<< "A utility that provides a user-friendly interface for configuring kernel commandline arguments that affect realtime audio performance on PREEMPT_DYANMIC kernels (like Ubuntu)."
|
||||||
|
<< "\n\n"
|
||||||
|
<< "Linux kernels that have PREEMPT_DYNAMIC kernels provide excellent support for real-time low-latency audio, but are not "
|
||||||
|
<< "optimially configured for real-time audio by default. "
|
||||||
|
<< "\n\n"
|
||||||
|
<< "To provide best performance, the following two kernel commandline arguments need to be set:"
|
||||||
|
<< "\n\n"
|
||||||
|
<< Indent(20)
|
||||||
|
<< HangingIndent()
|
||||||
|
<< " preempt=full\tallows the kernel to preempt kernel threads` in order to service more urgent real-time audio tasks."
|
||||||
|
<< "\n\n"
|
||||||
|
<< HangingIndent()
|
||||||
|
<< " threadirqs\tallows realtime audio tasks to run with higher priority than network and hard disk interrupt handlers."
|
||||||
|
<< "\n\n"
|
||||||
|
<< Indent(0)
|
||||||
|
<< "These settings have no effect on PREEMT_RT kernels (like Raspberry Pi OS), since PREEMPT_RT kernels turn these features on by default."
|
||||||
|
<< "\n\n"
|
||||||
|
<< "pipedal_kconfig needs to run with root privileges."
|
||||||
|
<< "\n\n"
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char**argv)
|
||||||
|
{
|
||||||
|
bool help = false;
|
||||||
|
bool noSudo = false;
|
||||||
|
CommandLineParser cmdline;
|
||||||
|
cmdline.AddOption("-h",&help);
|
||||||
|
cmdline.AddOption("--help",&help);
|
||||||
|
cmdline.AddOption("--no-sudo",&noSudo); // undocumented option to allow debugging of most of the code, except for the finial privileged bits.
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmdline.Parse(argc,(const char**)argv);
|
||||||
|
|
||||||
|
if (help || cmdline.Arguments().size() != 0)
|
||||||
|
{
|
||||||
|
printHelp();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto uid = getuid();
|
||||||
|
if (uid != 0 && !noSudo)
|
||||||
|
{
|
||||||
|
return SudoExec(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
kconfigUi();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user