mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 14:47:44 +00:00
Kernel+Services: Enable barebones hot-plug handling capabilities
Userspace initially didn't have any sort of mechanism to handle device hotplug (either removing or inserting a device). This meant that after a short term of scanning all known devices, by fetching device events (DeviceEvent packets) from /dev/devctl, we basically never try to read it again after SystemServer initialization code. To accommodate hotplug needs, we change SystemServer by ensuring it will generate a known set of device nodes at their location during the its main initialization code. This includes devices like /dev/mem, /dev/zero and /dev/full, etc. The actual responsible userspace program to handle hotplug events is a new userspace program called DeviceMapper, with following key points: - Its current task is to to constantly read the /dev/devctl device node. Because we already created generic devices, we only handle devices that are dynamically-generated in nature, like storage devices, audio channels, etc. - Since dynamically-generated device nodes could have an infinite minor numbers, but major numbers are decoded to a device type, we create an internal registry based on two structures - DeviceNodeFamily, and RegisteredDeviceNode. DeviceNodeFamily objects are attached in the main logic code, when handling a DeviceEvent device insertion packet. A DeviceNodeFamily object has an internal HashTable to hold objects of RegisteredDeviceNode class. - Because some device nodes could still share the same major number (TTY and serial TTY devices), we have two modes of allocation - limited allocation (so a range is defined for a major number), or infinite range. Therefore, two (or more) separate DeviceNodeFamily objects can can exist albeit sharing the same major number, but they are required to allocate from a different minor numbers' range to ensure there are no collisions. - As for KCOV, we handle this device differently. In case the user compiled the kernel with such support - this happens to be a singular device node that we usually don't need, so it's dynamically-generated too, and because it has only one instance, we don't register it in our internal registry to not make it complicated needlessly. The Kernel code is modified to allow proper blocking in case of no events in the DeviceControlDevice class, because otherwise we will need to poll periodically the device to check if a new event is available, which would waste CPU time for no good reason.
This commit is contained in:
parent
9dbd22b555
commit
446200d6f3
12 changed files with 535 additions and 348 deletions
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org>
|
||||
* Copyright (c) 2023, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -17,6 +18,7 @@
|
|||
#include <LibCore/Event.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/Process.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibMain/Main.h>
|
||||
#include <errno.h>
|
||||
|
@ -67,6 +69,8 @@ static void sigchld_handler(int)
|
|||
}
|
||||
}
|
||||
|
||||
namespace SystemServer {
|
||||
|
||||
static ErrorOr<void> determine_system_mode()
|
||||
{
|
||||
ArmedScopeGuard declare_text_mode_on_failure([&] {
|
||||
|
@ -93,280 +97,6 @@ static ErrorOr<void> determine_system_mode()
|
|||
declare_text_mode_on_failure.disarm();
|
||||
|
||||
dbgln("Read system_mode: {}", g_system_mode);
|
||||
|
||||
struct stat file_state;
|
||||
int rc = lstat("/dev/gpu/connector0", &file_state);
|
||||
if (rc != 0 && g_system_mode == "graphical") {
|
||||
dbgln("WARNING: No device nodes at /dev/gpu/ directory. This is probably a sign of disabled graphics functionality.");
|
||||
dbgln("To cope with this, I'll turn off graphical mode.");
|
||||
g_system_mode = "text";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> chown_all_matching_device_nodes_under_specific_directory(StringView directory, group const& group)
|
||||
{
|
||||
struct stat cur_file_stat;
|
||||
|
||||
Core::DirIterator di(directory, Core::DirIterator::SkipParentAndBaseDir);
|
||||
if (di.has_error())
|
||||
VERIFY_NOT_REACHED();
|
||||
while (di.has_next()) {
|
||||
auto entry_name = di.next_full_path();
|
||||
auto rc = stat(entry_name.characters(), &cur_file_stat);
|
||||
if (rc < 0)
|
||||
continue;
|
||||
TRY(Core::System::chown(entry_name, 0, group.gr_gid));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> chown_all_matching_device_nodes(group const& group, unsigned major_number)
|
||||
{
|
||||
struct stat cur_file_stat;
|
||||
|
||||
Core::DirIterator di("/dev/", Core::DirIterator::SkipParentAndBaseDir);
|
||||
if (di.has_error())
|
||||
VERIFY_NOT_REACHED();
|
||||
while (di.has_next()) {
|
||||
auto entry_name = di.next_full_path();
|
||||
auto rc = stat(entry_name.characters(), &cur_file_stat);
|
||||
if (rc < 0)
|
||||
continue;
|
||||
if (major(cur_file_stat.st_rdev) != major_number)
|
||||
continue;
|
||||
TRY(Core::System::chown(entry_name, 0, group.gr_gid));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
inline char offset_character_with_number(char base_char, u8 offset)
|
||||
{
|
||||
char offsetted_char = base_char;
|
||||
VERIFY(static_cast<size_t>(offsetted_char) + static_cast<size_t>(offset) < 256);
|
||||
offsetted_char += offset;
|
||||
return offsetted_char;
|
||||
}
|
||||
|
||||
static ErrorOr<void> create_devtmpfs_block_device(StringView name, mode_t mode, unsigned major, unsigned minor)
|
||||
{
|
||||
return Core::System::mknod(name, mode | S_IFBLK, makedev(major, minor));
|
||||
}
|
||||
|
||||
static ErrorOr<void> create_devtmpfs_char_device(StringView name, mode_t mode, unsigned major, unsigned minor)
|
||||
{
|
||||
return Core::System::mknod(name, mode | S_IFCHR, makedev(major, minor));
|
||||
}
|
||||
|
||||
static ErrorOr<bool> read_one_or_eof(NonnullOwnPtr<Core::File>& file, DeviceEvent& event)
|
||||
{
|
||||
auto const read_buf = TRY(file->read_some({ (u8*)&event, sizeof(DeviceEvent) }));
|
||||
if (read_buf.size() == sizeof(DeviceEvent)) {
|
||||
// Good! We could read another DeviceEvent.
|
||||
return true;
|
||||
}
|
||||
if (file->is_eof()) {
|
||||
// Good! We have reached the "natural" end of the file.
|
||||
return false;
|
||||
}
|
||||
// Bad! Kernel and SystemServer apparently disagree on the record size,
|
||||
// which means that previous data is likely to be invalid.
|
||||
return Error::from_string_view("File ended after incomplete record? /dev/devctl seems broken!"sv);
|
||||
}
|
||||
|
||||
static ErrorOr<void> populate_devtmpfs_devices_based_on_devctl()
|
||||
{
|
||||
auto file_or_error = Core::File::open("/dev/devctl"sv, Core::File::OpenMode::Read);
|
||||
if (file_or_error.is_error()) {
|
||||
warnln("Failed to open /dev/devctl - {}", file_or_error.error());
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
auto file = file_or_error.release_value();
|
||||
|
||||
DeviceEvent event;
|
||||
while (TRY(read_one_or_eof(file, event))) {
|
||||
if (event.state != DeviceEvent::Inserted)
|
||||
continue;
|
||||
auto major_number = event.major_number;
|
||||
auto minor_number = event.minor_number;
|
||||
bool is_block_device = (event.is_block_device == 1);
|
||||
switch (major_number) {
|
||||
case 116: {
|
||||
if (!is_block_device) {
|
||||
auto name = TRY(String::formatted("/dev/audio/{}", minor_number));
|
||||
TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0220, 116, minor_number));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 28: {
|
||||
auto name = TRY(String::formatted("/dev/gpu/render{}", minor_number));
|
||||
TRY(create_devtmpfs_block_device(name.bytes_as_string_view(), 0666, 28, minor_number));
|
||||
break;
|
||||
}
|
||||
case 226: {
|
||||
auto name = TRY(String::formatted("/dev/gpu/connector{}", minor_number));
|
||||
TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0666, 226, minor_number));
|
||||
break;
|
||||
}
|
||||
case 229: {
|
||||
if (!is_block_device) {
|
||||
auto name = TRY(String::formatted("/dev/hvc0p{}", minor_number));
|
||||
TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0666, 229, minor_number));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 10: {
|
||||
if (!is_block_device) {
|
||||
switch (minor_number) {
|
||||
case 0: {
|
||||
TRY(create_devtmpfs_char_device("/dev/input/mouse/0"sv, 0666, 10, 0));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
warnln("Unknown character device {}:{}", major_number, minor_number);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 85: {
|
||||
if (!is_block_device) {
|
||||
switch (minor_number) {
|
||||
case 0: {
|
||||
TRY(create_devtmpfs_char_device("/dev/input/keyboard/0"sv, 0666, 85, 0));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
warnln("Unknown character device {}:{}", major_number, minor_number);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
if (!is_block_device) {
|
||||
switch (minor_number) {
|
||||
case 5: {
|
||||
TRY(create_devtmpfs_char_device("/dev/zero"sv, 0666, 1, 5));
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
TRY(create_devtmpfs_char_device("/dev/mem"sv, 0666, 1, 1));
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
TRY(create_devtmpfs_char_device("/dev/null"sv, 0666, 1, 3));
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
TRY(create_devtmpfs_char_device("/dev/full"sv, 0666, 1, 7));
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
TRY(create_devtmpfs_char_device("/dev/random"sv, 0666, 1, 8));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
warnln("Unknown character device {}:{}", major_number, minor_number);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 30: {
|
||||
if (!is_block_device) {
|
||||
auto name = TRY(String::formatted("/dev/kcov{}", minor_number));
|
||||
TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0666, 30, minor_number));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
if (is_block_device) {
|
||||
auto name = TRY(String::formatted("/dev/hd{}", offset_character_with_number('a', minor_number)));
|
||||
TRY(create_devtmpfs_block_device(name.bytes_as_string_view(), 0600, 3, minor_number));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
if (!is_block_device) {
|
||||
switch (minor_number) {
|
||||
case 1: {
|
||||
TRY(create_devtmpfs_char_device("/dev/console"sv, 0666, 5, 1));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
TRY(create_devtmpfs_char_device("/dev/ptmx"sv, 0666, 5, 2));
|
||||
break;
|
||||
}
|
||||
case 0: {
|
||||
TRY(create_devtmpfs_char_device("/dev/tty"sv, 0666, 5, 0));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
warnln("Unknown character device {}:{}", major_number, minor_number);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
if (!is_block_device) {
|
||||
switch (minor_number) {
|
||||
case 0: {
|
||||
TRY(create_devtmpfs_char_device("/dev/tty0"sv, 0620, 4, 0));
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
TRY(create_devtmpfs_char_device("/dev/tty1"sv, 0620, 4, 1));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
TRY(create_devtmpfs_char_device("/dev/tty2"sv, 0620, 4, 2));
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
TRY(create_devtmpfs_char_device("/dev/tty3"sv, 0620, 4, 3));
|
||||
break;
|
||||
}
|
||||
case 64: {
|
||||
TRY(create_devtmpfs_char_device("/dev/ttyS0"sv, 0620, 4, 64));
|
||||
break;
|
||||
}
|
||||
case 65: {
|
||||
TRY(create_devtmpfs_char_device("/dev/ttyS1"sv, 0620, 4, 65));
|
||||
break;
|
||||
}
|
||||
case 66: {
|
||||
TRY(create_devtmpfs_char_device("/dev/ttyS2"sv, 0620, 4, 66));
|
||||
break;
|
||||
}
|
||||
case 67: {
|
||||
TRY(create_devtmpfs_char_device("/dev/ttyS3"sv, 0666, 4, 67));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
warnln("Unknown character device {}:{}", major_number, minor_number);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (!is_block_device)
|
||||
warnln("Unknown character device {}:{}", major_number, minor_number);
|
||||
else
|
||||
warnln("Unknown block device {}:{}", major_number, minor_number);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> populate_devtmpfs()
|
||||
{
|
||||
mode_t old_mask = umask(0);
|
||||
printf("Changing umask %#o\n", old_mask);
|
||||
TRY(create_devtmpfs_char_device("/dev/devctl"sv, 0660, 2, 10));
|
||||
TRY(populate_devtmpfs_devices_based_on_devctl());
|
||||
umask(old_mask);
|
||||
TRY(Core::System::symlink("/dev/random"sv, "/dev/urandom"sv));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -395,40 +125,26 @@ static ErrorOr<void> prepare_bare_minimum_devtmpfs_directory_structure()
|
|||
TRY(Core::System::mkdir("/dev/gpu"sv, 0755));
|
||||
TRY(Core::System::mkdir("/dev/pts"sv, 0755));
|
||||
TRY(Core::System::mount(-1, "/dev/pts"sv, "devpts"sv, 0));
|
||||
|
||||
mode_t old_mask = umask(0);
|
||||
TRY(Core::System::create_char_device("/dev/devctl"sv, 0660, 2, 10));
|
||||
TRY(Core::System::create_char_device("/dev/zero"sv, 0666, 1, 5));
|
||||
TRY(Core::System::create_char_device("/dev/mem"sv, 0600, 1, 1));
|
||||
TRY(Core::System::create_char_device("/dev/null"sv, 0666, 3, 0));
|
||||
TRY(Core::System::create_char_device("/dev/full"sv, 0666, 7, 0));
|
||||
TRY(Core::System::create_char_device("/dev/random"sv, 0666, 8, 0));
|
||||
TRY(Core::System::create_char_device("/dev/console"sv, 0666, 5, 1));
|
||||
TRY(Core::System::create_char_device("/dev/ptmx"sv, 0666, 5, 2));
|
||||
TRY(Core::System::create_char_device("/dev/tty"sv, 0666, 5, 0));
|
||||
umask(old_mask);
|
||||
TRY(Core::System::symlink("/dev/random"sv, "/dev/urandom"sv));
|
||||
TRY(Core::System::chmod("/dev/urandom"sv, 0666));
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> prepare_permissions_after_populating_devtmpfs()
|
||||
static ErrorOr<void> spawn_device_mapper_process()
|
||||
{
|
||||
TRY(Core::System::chmod("/dev/urandom"sv, 0666));
|
||||
|
||||
auto phys_group = TRY(Core::System::getgrnam("phys"sv));
|
||||
VERIFY(phys_group.has_value());
|
||||
// FIXME: Try to find a way to not hardcode the major number of display connector device nodes.
|
||||
TRY(chown_all_matching_device_nodes(phys_group.value(), 29));
|
||||
|
||||
auto const filter_chown_ENOENT = [](ErrorOr<void> result) -> ErrorOr<void> {
|
||||
auto const chown_enoent = Error::from_syscall("chown"sv, -ENOENT);
|
||||
if (result.is_error() && result.error() == chown_enoent) {
|
||||
dbgln("{}", result.release_error());
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
TRY(filter_chown_ENOENT(Core::System::chown("/dev/input/keyboard/0"sv, 0, phys_group.value().gr_gid)));
|
||||
TRY(filter_chown_ENOENT(Core::System::chown("/dev/input/mouse/0"sv, 0, phys_group.value().gr_gid)));
|
||||
|
||||
auto tty_group = TRY(Core::System::getgrnam("tty"sv));
|
||||
VERIFY(tty_group.has_value());
|
||||
// FIXME: Try to find a way to not hardcode the major number of tty nodes.
|
||||
TRY(chown_all_matching_device_nodes(tty_group.release_value(), 4));
|
||||
|
||||
auto audio_group = TRY(Core::System::getgrnam("audio"sv));
|
||||
VERIFY(audio_group.has_value());
|
||||
TRY(Core::System::chown("/dev/audio"sv, 0, audio_group->gr_gid));
|
||||
TRY(chown_all_matching_device_nodes_under_specific_directory("/dev/audio"sv, audio_group.release_value()));
|
||||
TRY(Core::System::endgrent());
|
||||
TRY(Core::Process::spawn("/bin/DeviceMapper"sv, ReadonlySpan<StringView> {}, {}, Core::Process::KeepAsChild::No));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -436,8 +152,7 @@ static ErrorOr<void> prepare_synthetic_filesystems()
|
|||
{
|
||||
TRY(prepare_bare_minimum_filesystem_mounts());
|
||||
TRY(prepare_bare_minimum_devtmpfs_directory_structure());
|
||||
TRY(populate_devtmpfs());
|
||||
TRY(prepare_permissions_after_populating_devtmpfs());
|
||||
TRY(spawn_device_mapper_process());
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -557,6 +272,8 @@ static ErrorOr<void> activate_user_services_based_on_system_mode()
|
|||
return {};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||
{
|
||||
bool user = false;
|
||||
|
@ -565,18 +282,18 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
args_parser.parse(arguments);
|
||||
|
||||
if (!user) {
|
||||
TRY(mount_all_filesystems());
|
||||
TRY(prepare_synthetic_filesystems());
|
||||
TRY(reopen_base_file_descriptors());
|
||||
TRY(SystemServer::mount_all_filesystems());
|
||||
TRY(SystemServer::prepare_synthetic_filesystems());
|
||||
TRY(SystemServer::reopen_base_file_descriptors());
|
||||
}
|
||||
|
||||
TRY(Core::System::pledge("stdio proc exec tty accept unix rpath wpath cpath chown fattr id sigaction"));
|
||||
|
||||
if (!user) {
|
||||
TRY(create_tmp_coredump_directory());
|
||||
TRY(set_default_coredump_directory());
|
||||
TRY(create_tmp_semaphore_directory());
|
||||
TRY(determine_system_mode());
|
||||
TRY(SystemServer::create_tmp_coredump_directory());
|
||||
TRY(SystemServer::set_default_coredump_directory());
|
||||
TRY(SystemServer::create_tmp_semaphore_directory());
|
||||
TRY(SystemServer::determine_system_mode());
|
||||
}
|
||||
|
||||
Core::EventLoop event_loop;
|
||||
|
@ -585,9 +302,9 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
event_loop.register_signal(SIGTERM, sigterm_handler);
|
||||
|
||||
if (!user) {
|
||||
TRY(activate_base_services_based_on_system_mode());
|
||||
TRY(SystemServer::activate_base_services_based_on_system_mode());
|
||||
} else {
|
||||
TRY(activate_user_services_based_on_system_mode());
|
||||
TRY(SystemServer::activate_user_services_based_on_system_mode());
|
||||
}
|
||||
|
||||
return event_loop.exec();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue