pipedal_kconfig (Ubuntu Pi/Uboot)

This commit is contained in:
Robin E. R. Davies
2024-11-29 08:38:11 -05:00
parent 505caad18f
commit eabe9b0f1c
12 changed files with 1213 additions and 35 deletions
+3 -1
View File
@@ -21,7 +21,9 @@
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [ ],
"args": [
//"-h"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
+5
View File
@@ -14,6 +14,11 @@ set (CMAKE_INSTALL_PREFIX "/usr/")
include(CTest)
enable_testing()
#add_subdirectory("submodules/pipedal_p2pd")
add_subdirectory("PiPedalCommon")
+34
View File
@@ -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;
}
}
+4
View File
@@ -43,5 +43,9 @@ namespace pipedal
void sysExecTerminate(ProcessId pid_, int termTimeoutMs = 1000, int killTimeoutMs = 500); // returns the process's exit status.
int sysExecWait(ProcessId pid);
// re-execute the current program with current argument, but with sudo.
int SudoExec(int argc, char **argv);
std::string getSelfExePath();
}
BIN
View File
Binary file not shown.
+329
View File
@@ -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;
}
}
+83
View File
@@ -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;
};
};
+38
View File
@@ -11,6 +11,29 @@ set (USE_SANITIZE OFF) # seems to be broken on Ubuntu 24.10
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)
find_package(sdbus-c++ REQUIRED)
@@ -303,6 +326,21 @@ if(${USE_PCH})
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
asan_options.cpp # disable leak checking for sanitize=address.
-32
View File
@@ -561,38 +561,6 @@ void InstallAudioService()
#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()
{
+1 -1
View File
@@ -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
// this software and associated documentation files (the "Software"), to deal in
+6 -1
View File
@@ -183,7 +183,12 @@ namespace pipedal
{
++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);
WriteLine();
+710
View File
@@ -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;
}