Skip to content

Commit 446200d

Browse files
supercomputer7ADKaster
authored andcommitted
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.
1 parent 9dbd22b commit 446200d

12 files changed

+535
-348
lines changed

Kernel/Devices/DeviceManagement.cpp

+13-16
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,6 @@ Device* DeviceManagement::get_device(MajorNumber major, MinorNumber minor)
5252
});
5353
}
5454

55-
Optional<DeviceEvent> DeviceManagement::dequeue_top_device_event(Badge<DeviceControlDevice>)
56-
{
57-
SpinlockLocker locker(m_event_queue_lock);
58-
if (m_event_queue.is_empty())
59-
return {};
60-
return m_event_queue.dequeue();
61-
}
62-
6355
void DeviceManagement::before_device_removal(Badge<Device>, Device& device)
6456
{
6557
u64 device_id = encoded_device(device.major(), device.minor());
@@ -68,15 +60,20 @@ void DeviceManagement::before_device_removal(Badge<Device>, Device& device)
6860
map.remove(encoded_device(device.major(), device.minor()));
6961
});
7062

71-
{
63+
m_event_queue.with([&](auto& queue) {
7264
DeviceEvent event { DeviceEvent::State::Removed, device.is_block_device(), device.major().value(), device.minor().value() };
73-
SpinlockLocker locker(m_event_queue_lock);
74-
m_event_queue.enqueue(event);
75-
}
65+
queue.enqueue(event);
66+
});
67+
7668
if (m_device_control_device)
7769
m_device_control_device->evaluate_block_conditions();
7870
}
7971

72+
SpinlockProtected<CircularQueue<DeviceEvent, 100>, LockRank::None>& DeviceManagement::event_queue(Badge<DeviceControlDevice>)
73+
{
74+
return m_event_queue;
75+
}
76+
8077
void DeviceManagement::after_inserting_device(Badge<Device>, Device& device)
8178
{
8279
u64 device_id = encoded_device(device.major(), device.minor());
@@ -92,11 +89,11 @@ void DeviceManagement::after_inserting_device(Badge<Device>, Device& device)
9289
}
9390
});
9491

95-
{
92+
m_event_queue.with([&](auto& queue) {
9693
DeviceEvent event { DeviceEvent::State::Inserted, device.is_block_device(), device.major().value(), device.minor().value() };
97-
SpinlockLocker locker(m_event_queue_lock);
98-
m_event_queue.enqueue(event);
99-
}
94+
queue.enqueue(event);
95+
});
96+
10097
if (m_device_control_device)
10198
m_device_control_device->evaluate_block_conditions();
10299
}

Kernel/Devices/DeviceManagement.h

+3-5
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ class DeviceManagement {
3636
bool is_console_device_attached() const { return !m_console_device.is_null(); }
3737
void attach_console_device(ConsoleDevice const&);
3838

39-
Optional<DeviceEvent> dequeue_top_device_event(Badge<DeviceControlDevice>);
40-
4139
void after_inserting_device(Badge<Device>, Device&);
4240
void before_device_removal(Badge<Device>, Device&);
4341

@@ -68,14 +66,14 @@ class DeviceManagement {
6866
return device;
6967
}
7068

69+
SpinlockProtected<CircularQueue<DeviceEvent, 100>, LockRank::None>& event_queue(Badge<DeviceControlDevice>);
70+
7171
private:
7272
LockRefPtr<NullDevice> m_null_device;
7373
LockRefPtr<ConsoleDevice> m_console_device;
7474
LockRefPtr<DeviceControlDevice> m_device_control_device;
7575
SpinlockProtected<HashMap<u64, Device*>, LockRank::None> m_devices {};
76-
77-
mutable Spinlock<LockRank::None> m_event_queue_lock {};
78-
CircularQueue<DeviceEvent, 100> m_event_queue;
76+
SpinlockProtected<CircularQueue<DeviceEvent, 100>, LockRank::None> m_event_queue {};
7977
};
8078

8179
}

Kernel/Devices/Generic/DeviceControlDevice.cpp

+19-11
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ UNMAP_AFTER_INIT NonnullLockRefPtr<DeviceControlDevice> DeviceControlDevice::mus
1919

2020
bool DeviceControlDevice::can_read(OpenFileDescription const&, u64) const
2121
{
22-
return true;
22+
return DeviceManagement::the().event_queue({}).with([](auto& queue) -> bool {
23+
return !queue.is_empty();
24+
});
2325
}
2426

2527
UNMAP_AFTER_INIT DeviceControlDevice::DeviceControlDevice()
@@ -29,18 +31,24 @@ UNMAP_AFTER_INIT DeviceControlDevice::DeviceControlDevice()
2931

3032
UNMAP_AFTER_INIT DeviceControlDevice::~DeviceControlDevice() = default;
3133

32-
ErrorOr<size_t> DeviceControlDevice::read(OpenFileDescription&, u64, UserOrKernelBuffer& buffer, size_t size)
34+
ErrorOr<size_t> DeviceControlDevice::read(OpenFileDescription&, u64 offset, UserOrKernelBuffer& buffer, size_t size)
3335
{
34-
auto device_event = DeviceManagement::the().dequeue_top_device_event({});
35-
if (!device_event.has_value())
36-
return 0;
37-
38-
if (size < sizeof(DeviceEvent))
36+
if (offset != 0)
37+
return Error::from_errno(EINVAL);
38+
if ((size % sizeof(DeviceEvent)) != 0)
3939
return Error::from_errno(EOVERFLOW);
40-
size_t nread = 0;
41-
TRY(buffer.write(&device_event.value(), nread, sizeof(DeviceEvent)));
42-
nread += sizeof(DeviceEvent);
43-
return nread;
40+
41+
return DeviceManagement::the().event_queue({}).with([&](auto& queue) -> ErrorOr<size_t> {
42+
size_t nread = 0;
43+
for (size_t event_index = 0; event_index < (size / sizeof(DeviceEvent)); event_index++) {
44+
if (queue.is_empty())
45+
break;
46+
auto event = queue.dequeue();
47+
TRY(buffer.write(&event, nread, sizeof(DeviceEvent)));
48+
nread += sizeof(DeviceEvent);
49+
}
50+
return nread;
51+
});
4452
}
4553

4654
ErrorOr<void> DeviceControlDevice::ioctl(OpenFileDescription&, unsigned, Userspace<void*>)

Userland/Services/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ if (SERENITYOS)
1010
add_subdirectory(ChessEngine)
1111
add_subdirectory(Clipboard)
1212
add_subdirectory(CrashDaemon)
13+
add_subdirectory(DeviceMapper)
1314
add_subdirectory(DHCPClient)
1415
add_subdirectory(FileSystemAccessServer)
1516
add_subdirectory(KeyboardPreferenceLoader)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
serenity_component(
2+
DeviceMapper
3+
REQUIRED
4+
TARGETS DeviceMapper
5+
)
6+
7+
set(SOURCES
8+
main.cpp
9+
DeviceEventLoop.cpp
10+
)
11+
12+
serenity_bin(DeviceMapper)
13+
target_link_libraries(DeviceMapper PRIVATE LibCore LibFileSystem LibMain)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*
2+
* Copyright (c) 2023, Liav A. <[email protected]>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include "DeviceEventLoop.h"
8+
#include <AK/Debug.h>
9+
#include <LibCore/DirIterator.h>
10+
#include <LibCore/System.h>
11+
#include <LibIPC/MultiServer.h>
12+
#include <fcntl.h>
13+
#include <stdio.h>
14+
#include <unistd.h>
15+
16+
namespace DeviceMapper {
17+
18+
DeviceEventLoop::DeviceEventLoop(NonnullOwnPtr<Core::File> devctl_file)
19+
: m_devctl_file(move(devctl_file))
20+
{
21+
}
22+
23+
using MinorNumberAllocationType = DeviceEventLoop::MinorNumberAllocationType;
24+
25+
static constexpr DeviceEventLoop::DeviceNodeMatch s_matchers[] = {
26+
{ "audio"sv, "audio"sv, "audio/%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 116, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0220 },
27+
{ {}, "render"sv, "gpu/render%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 28, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 },
28+
{ "window"sv, "gpu-connector"sv, "gpu/connector%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 226, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0660 },
29+
{ {}, "virtio-console"sv, "hvc0p%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 229, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 },
30+
{ "phys"sv, "hid-mouse"sv, "input/mouse/%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 10, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 },
31+
{ "phys"sv, "hid-keyboard"sv, "input/keyboard/%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 85, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 },
32+
{ {}, "storage"sv, "hd%letter"sv, DeviceNodeFamily::Type::BlockDevice, 3, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0600 },
33+
{ "tty"sv, "console"sv, "tty%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 4, MinorNumberAllocationType::SequentialLimited, 0, 63, 0620 },
34+
{ "tty"sv, "console"sv, "ttyS%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 4, MinorNumberAllocationType::SequentialLimited, 64, 127, 0620 },
35+
};
36+
37+
static bool is_in_minor_number_range(DeviceEventLoop::DeviceNodeMatch const& matcher, MinorNumber minor_number)
38+
{
39+
if (matcher.minor_number_allocation_type == MinorNumberAllocationType::SequentialUnlimited)
40+
return true;
41+
42+
return matcher.minor_number_start <= minor_number && static_cast<MinorNumber>(matcher.minor_number_start.value() + matcher.minor_number_range_size) >= minor_number;
43+
}
44+
45+
static Optional<DeviceEventLoop::DeviceNodeMatch const&> device_node_family_to_match_type(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number)
46+
{
47+
for (auto& matcher : s_matchers) {
48+
if (matcher.major_number == major_number
49+
&& unix_device_type == matcher.unix_device_type
50+
&& is_in_minor_number_range(matcher, minor_number))
51+
return matcher;
52+
}
53+
return {};
54+
}
55+
56+
static bool is_in_family_minor_number_range(DeviceNodeFamily const& family, MinorNumber minor_number)
57+
{
58+
return family.base_minor_number() <= minor_number && static_cast<MinorNumber>(family.base_minor_number().value() + family.devices_symbol_suffix_allocation_map().size()) >= minor_number;
59+
}
60+
61+
Optional<DeviceNodeFamily&> DeviceEventLoop::find_device_node_family(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) const
62+
{
63+
for (auto const& family : m_device_node_families) {
64+
if (family->major_number() == major_number && family->type() == unix_device_type && is_in_family_minor_number_range(*family, minor_number))
65+
return *family.ptr();
66+
}
67+
return {};
68+
}
69+
70+
ErrorOr<NonnullRefPtr<DeviceNodeFamily>> DeviceEventLoop::find_or_register_new_device_node_family(DeviceNodeMatch const& match, DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number)
71+
{
72+
if (auto possible_family = find_device_node_family(unix_device_type, major_number, minor_number); possible_family.has_value())
73+
return possible_family.release_value();
74+
unsigned allocation_map_size = 1024;
75+
if (match.minor_number_allocation_type == MinorNumberAllocationType::SequentialLimited)
76+
allocation_map_size = match.minor_number_range_size;
77+
auto bitmap = TRY(Bitmap::create(allocation_map_size, false));
78+
auto node = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) DeviceNodeFamily(move(bitmap),
79+
match.family_type_literal,
80+
unix_device_type,
81+
major_number,
82+
minor_number)));
83+
TRY(m_device_node_families.try_append(node));
84+
85+
return node;
86+
}
87+
88+
static ErrorOr<String> build_suffix_with_letters(size_t allocation_index)
89+
{
90+
auto base_string = TRY(String::from_utf8(""sv));
91+
while (true) {
92+
base_string = TRY(String::formatted("{:c}{}", 'a' + (allocation_index % 26), base_string));
93+
allocation_index = (allocation_index / 26);
94+
if (allocation_index == 0)
95+
break;
96+
allocation_index = allocation_index - 1;
97+
}
98+
return base_string;
99+
}
100+
101+
static ErrorOr<String> build_suffix_with_numbers(size_t allocation_index)
102+
{
103+
return String::number(allocation_index);
104+
}
105+
106+
static ErrorOr<void> prepare_permissions_after_populating_devtmpfs(StringView path, DeviceEventLoop::DeviceNodeMatch const& match)
107+
{
108+
if (match.permission_group.is_null())
109+
return {};
110+
auto group = TRY(Core::System::getgrnam(match.permission_group));
111+
VERIFY(group.has_value());
112+
TRY(Core::System::endgrent());
113+
TRY(Core::System::chown(path, 0, group.value().gr_gid));
114+
return {};
115+
}
116+
117+
ErrorOr<void> DeviceEventLoop::register_new_device(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number)
118+
{
119+
auto possible_match = device_node_family_to_match_type(unix_device_type, major_number, minor_number);
120+
if (!possible_match.has_value())
121+
return {};
122+
auto const& match = possible_match.release_value();
123+
auto device_node_family = TRY(find_or_register_new_device_node_family(match, unix_device_type, major_number, minor_number));
124+
static constexpr StringView devtmpfs_base_path = "/dev/"sv;
125+
auto path_pattern = TRY(String::from_utf8(match.path_pattern));
126+
auto& allocation_map = device_node_family->devices_symbol_suffix_allocation_map();
127+
auto possible_allocated_suffix_index = allocation_map.find_first_unset();
128+
if (!possible_allocated_suffix_index.has_value()) {
129+
// FIXME: Make the allocation map bigger?
130+
return Error::from_errno(ERANGE);
131+
}
132+
auto allocated_suffix_index = possible_allocated_suffix_index.release_value();
133+
134+
auto path = TRY(String::from_utf8(path_pattern));
135+
if (match.path_pattern.contains("%digit"sv)) {
136+
auto replacement = TRY(build_suffix_with_numbers(allocated_suffix_index));
137+
path = TRY(path.replace("%digit"sv, replacement, ReplaceMode::All));
138+
}
139+
if (match.path_pattern.contains("%letter"sv)) {
140+
auto replacement = TRY(build_suffix_with_letters(allocated_suffix_index));
141+
path = TRY(path.replace("%letter"sv, replacement, ReplaceMode::All));
142+
}
143+
VERIFY(!path.is_empty());
144+
path = TRY(String::formatted("{}{}", devtmpfs_base_path, path));
145+
mode_t old_mask = umask(0);
146+
if (unix_device_type == DeviceNodeFamily::Type::BlockDevice)
147+
TRY(Core::System::create_block_device(path.bytes_as_string_view(), match.create_mode, major_number.value(), minor_number.value()));
148+
else
149+
TRY(Core::System::create_char_device(path.bytes_as_string_view(), match.create_mode, major_number.value(), minor_number.value()));
150+
umask(old_mask);
151+
TRY(prepare_permissions_after_populating_devtmpfs(path.bytes_as_string_view(), match));
152+
153+
auto result = TRY(device_node_family->registered_nodes().try_set(RegisteredDeviceNode { move(path), minor_number }, AK::HashSetExistingEntryBehavior::Keep));
154+
VERIFY(result != HashSetResult::ReplacedExistingEntry);
155+
if (result == HashSetResult::KeptExistingEntry) {
156+
// FIXME: Handle this case properly.
157+
return Error::from_errno(EEXIST);
158+
}
159+
allocation_map.set(allocated_suffix_index, true);
160+
return {};
161+
}
162+
163+
ErrorOr<void> DeviceEventLoop::unregister_device(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number)
164+
{
165+
if (!device_node_family_to_match_type(unix_device_type, major_number, minor_number).has_value())
166+
return {};
167+
auto possible_family = find_device_node_family(unix_device_type, major_number, minor_number);
168+
if (!possible_family.has_value()) {
169+
// FIXME: Handle cases where we can't remove a device node.
170+
// This could happen when the DeviceMapper program was restarted
171+
// so the previous state was not preserved and a device was removed.
172+
return Error::from_errno(ENODEV);
173+
}
174+
auto& family = possible_family.release_value();
175+
for (auto& node : family.registered_nodes()) {
176+
if (node.minor_number() == minor_number)
177+
TRY(Core::System::unlink(node.device_path()));
178+
}
179+
auto removed_anything = family.registered_nodes().remove_all_matching([minor_number](auto& device) { return device.minor_number() == minor_number; });
180+
if (!removed_anything) {
181+
// FIXME: Handle cases where we can't remove a device node.
182+
// This could happen when the DeviceMapper program was restarted
183+
// so the previous state was not preserved and a device was removed.
184+
return Error::from_errno(ENODEV);
185+
}
186+
return {};
187+
}
188+
189+
static ErrorOr<void> create_kcov_device_node()
190+
{
191+
mode_t old_mask = umask(0);
192+
ScopeGuard umask_guard([old_mask] { umask(old_mask); });
193+
TRY(Core::System::create_char_device("/dev/kcov"sv, 0666, 30, 0));
194+
return {};
195+
}
196+
197+
ErrorOr<void> DeviceEventLoop::read_one_or_eof(DeviceEvent& event)
198+
{
199+
if (m_devctl_file->read_until_filled({ bit_cast<u8*>(&event), sizeof(DeviceEvent) }).is_error()) {
200+
// Bad! Kernel and SystemServer apparently disagree on the record size,
201+
// which means that previous data is likely to be invalid.
202+
return Error::from_string_view("File ended after incomplete record? /dev/devctl seems broken!"sv);
203+
}
204+
return {};
205+
}
206+
207+
ErrorOr<void> DeviceEventLoop::drain_events_from_devctl()
208+
{
209+
for (;;) {
210+
DeviceEvent event;
211+
TRY(read_one_or_eof(event));
212+
// NOTE: Ignore any event related to /dev/devctl device node - normally
213+
// it should never disappear from the system and we already use it in this
214+
// code.
215+
if (event.major_number == 2 && event.minor_number == 10 && !event.is_block_device)
216+
continue;
217+
218+
if (event.state == DeviceEvent::State::Inserted) {
219+
// NOTE: We have a special function for the KCOV device, because we don't
220+
// want to create a new MinorNumberAllocationType (e.g. SingleInstance).
221+
// Instead, just blindly create that device node and assume we will never
222+
// have to worry about it, so we don't need to register that!
223+
if (event.major_number == 30 && event.minor_number == 0 && !event.is_block_device) {
224+
TRY(create_kcov_device_node());
225+
continue;
226+
}
227+
VERIFY(event.is_block_device == 1 || event.is_block_device == 0);
228+
TRY(register_new_device(event.is_block_device ? DeviceNodeFamily::Type::BlockDevice : DeviceNodeFamily::Type::CharacterDevice, event.major_number, event.minor_number));
229+
} else if (event.state == DeviceEvent::State::Removed) {
230+
if (event.major_number == 30 && event.minor_number == 0 && !event.is_block_device) {
231+
dbgln("DeviceMapper: unregistering device failed: kcov tried to de-register itself!?");
232+
continue;
233+
}
234+
if (auto error_or_void = unregister_device(event.is_block_device ? DeviceNodeFamily::Type::BlockDevice : DeviceNodeFamily::Type::CharacterDevice, event.major_number, event.minor_number); error_or_void.is_error())
235+
dbgln("DeviceMapper: unregistering device failed: {}", error_or_void.error());
236+
} else {
237+
dbgln("DeviceMapper: Unhandled device event ({:x})!", event.state);
238+
}
239+
}
240+
VERIFY_NOT_REACHED();
241+
}
242+
243+
}

0 commit comments

Comments
 (0)