Serenity Operating System
1/*
2 * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <Kernel/API/Ioctl.h>
8#include <Kernel/FileSystem/SysFS/Subsystems/DeviceIdentifiers/CharacterDevicesDirectory.h>
9#include <Kernel/FileSystem/SysFS/Subsystems/Devices/Graphics/DisplayConnector/DeviceDirectory.h>
10#include <Kernel/FileSystem/SysFS/Subsystems/Devices/Graphics/DisplayConnector/Directory.h>
11#include <Kernel/Graphics/DisplayConnector.h>
12#include <Kernel/Graphics/GraphicsManagement.h>
13#include <Kernel/Memory/MemoryManager.h>
14
15namespace Kernel {
16
17DisplayConnector::DisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, bool enable_write_combine_optimization)
18 : CharacterDevice(226, GraphicsManagement::the().allocate_minor_device_number())
19 , m_enable_write_combine_optimization(enable_write_combine_optimization)
20 , m_framebuffer_at_arbitrary_physical_range(false)
21 , m_framebuffer_address(framebuffer_address)
22 , m_framebuffer_resource_size(framebuffer_resource_size)
23{
24}
25
26DisplayConnector::DisplayConnector(size_t framebuffer_resource_size, bool enable_write_combine_optimization)
27 : CharacterDevice(226, GraphicsManagement::the().allocate_minor_device_number())
28 , m_enable_write_combine_optimization(enable_write_combine_optimization)
29 , m_framebuffer_at_arbitrary_physical_range(true)
30 , m_framebuffer_address({})
31 , m_framebuffer_resource_size(framebuffer_resource_size)
32{
33}
34
35ErrorOr<NonnullLockRefPtr<Memory::VMObject>> DisplayConnector::vmobject_for_mmap(Process&, Memory::VirtualRange const&, u64& offset, bool)
36{
37 VERIFY(m_shared_framebuffer_vmobject);
38 if (offset != 0)
39 return Error::from_errno(ENOTSUP);
40
41 return *m_shared_framebuffer_vmobject;
42}
43
44ErrorOr<size_t> DisplayConnector::read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t)
45{
46 return Error::from_errno(ENOTIMPL);
47}
48
49ErrorOr<size_t> DisplayConnector::write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t)
50{
51 return Error::from_errno(ENOTIMPL);
52}
53
54void DisplayConnector::will_be_destroyed()
55{
56 GraphicsManagement::the().detach_display_connector({}, *this);
57
58 // NOTE: We check if m_symlink_sysfs_component is not null, because if we failed
59 // at some point in DisplayConnector::after_inserting(), then that method will tear down
60 // the object internal members safely, so we don't want to do it again here.
61 if (m_symlink_sysfs_component) {
62 before_will_be_destroyed_remove_symlink_from_device_identifier_directory();
63 m_symlink_sysfs_component.clear();
64 }
65
66 // NOTE: We check if m_sysfs_device_directory is not null, because if we failed
67 // at some point in DisplayConnector::after_inserting(), then that method will tear down
68 // the object internal members safely, so we don't want to do it again here.
69 if (m_sysfs_device_directory) {
70 SysFSDisplayConnectorsDirectory::the().unplug({}, *m_sysfs_device_directory);
71 m_sysfs_device_directory.clear();
72 }
73
74 before_will_be_destroyed_remove_from_device_management();
75}
76
77ErrorOr<void> DisplayConnector::allocate_framebuffer_resources(size_t rounded_size)
78{
79 VERIFY((rounded_size % PAGE_SIZE) == 0);
80 if (!m_framebuffer_at_arbitrary_physical_range) {
81 VERIFY(m_framebuffer_address.value().page_base() == m_framebuffer_address.value());
82 m_shared_framebuffer_vmobject = TRY(Memory::SharedFramebufferVMObject::try_create_for_physical_range(m_framebuffer_address.value(), rounded_size));
83 m_framebuffer_region = TRY(MM.allocate_kernel_region(m_framebuffer_address.value().page_base(), rounded_size, "Framebuffer"sv, Memory::Region::Access::ReadWrite));
84 } else {
85 m_shared_framebuffer_vmobject = TRY(Memory::SharedFramebufferVMObject::try_create_at_arbitrary_physical_range(rounded_size));
86 m_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(m_shared_framebuffer_vmobject->real_writes_framebuffer_vmobject(), rounded_size, "Framebuffer"sv, Memory::Region::Access::ReadWrite));
87 }
88
89 m_framebuffer_data = m_framebuffer_region->vaddr().as_ptr();
90 m_fake_writes_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(m_shared_framebuffer_vmobject->fake_writes_framebuffer_vmobject(), rounded_size, "Fake Writes Framebuffer"sv, Memory::Region::Access::ReadWrite));
91 return {};
92}
93
94ErrorOr<void> DisplayConnector::after_inserting()
95{
96 after_inserting_add_to_device_management();
97 ArmedScopeGuard clean_from_device_management([&] {
98 before_will_be_destroyed_remove_from_device_management();
99 });
100
101 auto sysfs_display_connector_device_directory = DisplayConnectorSysFSDirectory::create(SysFSDisplayConnectorsDirectory::the(), *this);
102 m_sysfs_device_directory = sysfs_display_connector_device_directory;
103 SysFSDisplayConnectorsDirectory::the().plug({}, *sysfs_display_connector_device_directory);
104 ArmedScopeGuard clean_from_sysfs_display_connector_device_directory([&] {
105 SysFSDisplayConnectorsDirectory::the().unplug({}, *m_sysfs_device_directory);
106 m_sysfs_device_directory.clear();
107 });
108
109 VERIFY(!m_symlink_sysfs_component);
110 auto sys_fs_component = TRY(SysFSSymbolicLinkDeviceComponent::try_create(SysFSCharacterDevicesDirectory::the(), *this, *m_sysfs_device_directory));
111 m_symlink_sysfs_component = sys_fs_component;
112 after_inserting_add_symlink_to_device_identifier_directory();
113 ArmedScopeGuard clean_symlink_to_device_identifier_directory([&] {
114 VERIFY(m_symlink_sysfs_component);
115 before_will_be_destroyed_remove_symlink_from_device_identifier_directory();
116 m_symlink_sysfs_component.clear();
117 });
118
119 if (auto result_or_error = Memory::page_round_up(m_framebuffer_resource_size); result_or_error.is_error()) {
120 // NOTE: The amount of framebuffer resource being specified is erroneous, then default to 16 MiB.
121 TRY(allocate_framebuffer_resources(16 * MiB));
122 m_framebuffer_resource_size = 16 * MiB;
123 } else {
124 if (auto allocation_result = allocate_framebuffer_resources(result_or_error.release_value()); allocation_result.is_error()) {
125 // NOTE: The amount of framebuffer resource being specified is too big, use 16 MiB just to get going.
126 TRY(allocate_framebuffer_resources(16 * MiB));
127 m_framebuffer_resource_size = 16 * MiB;
128 }
129 }
130
131 clean_from_device_management.disarm();
132 clean_from_sysfs_display_connector_device_directory.disarm();
133 clean_symlink_to_device_identifier_directory.disarm();
134
135 GraphicsManagement::the().attach_new_display_connector({}, *this);
136 if (m_enable_write_combine_optimization) {
137 [[maybe_unused]] auto result = m_framebuffer_region->set_write_combine(true);
138 }
139 return {};
140}
141
142bool DisplayConnector::console_mode() const
143{
144 VERIFY(m_control_lock.is_locked());
145 return m_console_mode;
146}
147
148void DisplayConnector::set_display_mode(Badge<GraphicsManagement>, DisplayMode mode)
149{
150 SpinlockLocker locker(m_control_lock);
151
152 {
153 SpinlockLocker locker(m_modeset_lock);
154 [[maybe_unused]] auto result = set_y_offset(0);
155 }
156
157 m_console_mode = mode == DisplayMode::Console ? true : false;
158 if (m_console_mode) {
159 VERIFY(m_framebuffer_region->size() == m_fake_writes_framebuffer_region->size());
160 memcpy(m_fake_writes_framebuffer_region->vaddr().as_ptr(), m_framebuffer_region->vaddr().as_ptr(), m_framebuffer_region->size());
161 m_shared_framebuffer_vmobject->switch_to_fake_sink_framebuffer_writes({});
162 enable_console();
163 } else {
164 disable_console();
165 m_shared_framebuffer_vmobject->switch_to_real_framebuffer_writes({});
166 VERIFY(m_framebuffer_region->size() == m_fake_writes_framebuffer_region->size());
167 memcpy(m_framebuffer_region->vaddr().as_ptr(), m_fake_writes_framebuffer_region->vaddr().as_ptr(), m_framebuffer_region->size());
168 }
169}
170
171ErrorOr<void> DisplayConnector::initialize_edid_for_generic_monitor(Optional<Array<u8, 3>> possible_manufacturer_id_string)
172{
173 u8 raw_manufacturer_id[2] = { 0x0, 0x0 };
174 if (possible_manufacturer_id_string.has_value()) {
175 Array<u8, 3> manufacturer_id_string = possible_manufacturer_id_string.release_value();
176 u8 byte1 = (((static_cast<u8>(manufacturer_id_string[0]) - '@') & 0x1f) << 2) | (((static_cast<u8>(manufacturer_id_string[1]) - '@') >> 3) & 3);
177 u8 byte2 = ((static_cast<u8>(manufacturer_id_string[2]) - '@') & 0x1f) | (((static_cast<u8>(manufacturer_id_string[1]) - '@') << 5) & 0xe0);
178 Array<u8, 2> manufacturer_id_string_packed_bytes = { byte1, byte2 };
179 raw_manufacturer_id[0] = manufacturer_id_string_packed_bytes[1];
180 raw_manufacturer_id[1] = manufacturer_id_string_packed_bytes[0];
181 }
182
183 Array<u8, 128> virtual_monitor_edid = {
184 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, /* header */
185 raw_manufacturer_id[1], raw_manufacturer_id[0], /* manufacturer */
186 0x00, 0x00, /* product code */
187 0x00, 0x00, 0x00, 0x00, /* serial number goes here */
188 0x01, /* week of manufacture */
189 0x00, /* year of manufacture */
190 0x01, 0x03, /* EDID version */
191 0x80, /* capabilities - digital */
192 0x00, /* horiz. res in cm, zero for projectors */
193 0x00, /* vert. res in cm */
194 0x78, /* display gamma (120 == 2.2). */
195 0xEE, /* features (standby, suspend, off, RGB, std */
196 /* colour space, preferred timing mode) */
197 0xEE, 0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54,
198 /* chromaticity for standard colour space. */
199 0x00, 0x00, 0x00, /* no default timings */
200 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
201 0x01, 0x01,
202 0x01, 0x01, 0x01, 0x01, /* no standard timings */
203 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x02, 0x02,
204 0x02, 0x02,
205 /* descriptor block 1 goes below */
206 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
207 /* descriptor block 2, monitor ranges */
208 0x00, 0x00, 0x00, 0xFD, 0x00,
209 0x00, 0xC8, 0x00, 0xC8, 0x64, 0x00, 0x0A, 0x20, 0x20, 0x20,
210 0x20, 0x20,
211 /* 0-200Hz vertical, 0-200KHz horizontal, 1000MHz pixel clock */
212 0x20,
213 /* descriptor block 3, monitor name */
214 0x00, 0x00, 0x00, 0xFC, 0x00,
215 'G', 'e', 'n', 'e', 'r', 'i', 'c', 'S', 'c', 'r', 'e', 'e', 'n',
216 /* descriptor block 4: dummy data */
217 0x00, 0x00, 0x00, 0x10, 0x00,
218 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,
219 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
220 0x20,
221 0x00, /* number of extensions */
222 0x00 /* checksum goes here */
223 };
224 // Note: Fix checksum to avoid warnings about checksum mismatch.
225 size_t checksum = 0;
226 // Note: Read all 127 bytes to add them to the checksum. Byte 128 is zeroed so
227 // we could technically add it to the sum result, but it could lead to an error if it contained
228 // a non-zero value, so we are not using it.
229 for (size_t index = 0; index < sizeof(virtual_monitor_edid) - 1; index++)
230 checksum += virtual_monitor_edid[index];
231 virtual_monitor_edid[127] = 0x100 - checksum;
232 set_edid_bytes(virtual_monitor_edid);
233 return {};
234}
235
236void DisplayConnector::set_edid_bytes(Array<u8, 128> const& edid_bytes, bool might_be_invalid)
237{
238 memcpy((u8*)m_edid_bytes, edid_bytes.data(), sizeof(m_edid_bytes));
239 if (auto parsed_edid = EDID::Parser::from_bytes({ m_edid_bytes, sizeof(m_edid_bytes) }); !parsed_edid.is_error()) {
240 m_edid_parser = parsed_edid.release_value();
241 m_edid_valid = true;
242 } else {
243 if (!might_be_invalid) {
244 dmesgln("DisplayConnector: Print offending EDID");
245 for (size_t x = 0; x < 128; x = x + 16) {
246 dmesgln("{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
247 m_edid_bytes[x], m_edid_bytes[x + 1], m_edid_bytes[x + 2], m_edid_bytes[x + 3],
248 m_edid_bytes[x + 4], m_edid_bytes[x + 5], m_edid_bytes[x + 6], m_edid_bytes[x + 7],
249 m_edid_bytes[x + 8], m_edid_bytes[x + 9], m_edid_bytes[x + 10], m_edid_bytes[x + 11],
250 m_edid_bytes[x + 12], m_edid_bytes[x + 13], m_edid_bytes[x + 14], m_edid_bytes[x + 15]);
251 }
252 dmesgln("DisplayConnector: Parsing EDID failed: {}", parsed_edid.error());
253 }
254 }
255}
256
257ErrorOr<void> DisplayConnector::flush_rectangle(size_t, FBRect const&)
258{
259 return Error::from_errno(ENOTSUP);
260}
261
262DisplayConnector::ModeSetting DisplayConnector::current_mode_setting() const
263{
264 SpinlockLocker locker(m_modeset_lock);
265 return m_current_mode_setting;
266}
267
268ErrorOr<ByteBuffer> DisplayConnector::get_edid() const
269{
270 if (!m_edid_valid)
271 return Error::from_errno(ENODEV);
272 return ByteBuffer::copy(m_edid_bytes, sizeof(m_edid_bytes));
273}
274
275struct GraphicsIOCtlChecker {
276 unsigned ioctl_number;
277 StringView name;
278 bool requires_ownership { false };
279};
280
281static constexpr GraphicsIOCtlChecker s_checkers[] = {
282 { GRAPHICS_IOCTL_GET_PROPERTIES, "GRAPHICS_IOCTL_GET_PROPERTIES"sv, false },
283 { GRAPHICS_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER, "GRAPHICS_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER"sv, true },
284 { GRAPHICS_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER, "GRAPHICS_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER"sv, false },
285 { GRAPHICS_IOCTL_FLUSH_HEAD_BUFFERS, "GRAPHICS_IOCTL_FLUSH_HEAD_BUFFERS"sv, true },
286 { GRAPHICS_IOCTL_FLUSH_HEAD, "GRAPHICS_IOCTL_FLUSH_HEAD"sv, true },
287 { GRAPHICS_IOCTL_SET_HEAD_MODE_SETTING, "GRAPHICS_IOCTL_SET_HEAD_MODE_SETTING"sv, true },
288 { GRAPHICS_IOCTL_GET_HEAD_MODE_SETTING, "GRAPHICS_IOCTL_GET_HEAD_MODE_SETTING"sv, false },
289 { GRAPHICS_IOCTL_SET_SAFE_HEAD_MODE_SETTING, "GRAPHICS_IOCTL_SET_SAFE_HEAD_MODE_SETTING"sv, true },
290 { GRAPHICS_IOCTL_SET_RESPONSIBLE, "GRAPHICS_IOCTL_SET_RESPONSIBLE"sv, false },
291 { GRAPHICS_IOCTL_UNSET_RESPONSIBLE, "GRAPHICS_IOCTL_UNSET_RESPONSIBLE"sv, true },
292};
293
294static StringView ioctl_to_stringview(unsigned request)
295{
296 for (auto& checker : s_checkers) {
297 if (checker.ioctl_number == request)
298 return checker.name;
299 }
300 return "unknown"sv;
301}
302
303ErrorOr<bool> DisplayConnector::ioctl_requires_ownership(unsigned request) const
304{
305 for (auto& checker : s_checkers) {
306 if (checker.ioctl_number == request)
307 return checker.requires_ownership;
308 }
309 // Note: In case of unknown ioctl, return EINVAL.
310 return Error::from_errno(EINVAL);
311}
312
313ErrorOr<void> DisplayConnector::ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg)
314{
315 TRY(Process::current().require_promise(Pledge::video));
316
317 // Note: We only allow to set responsibility on a DisplayConnector,
318 // get the current ModeSetting or the hardware framebuffer properties without the
319 // need of having an established responsibility on a DisplayConnector.
320 auto needs_ownership = TRY(ioctl_requires_ownership(request));
321 if (needs_ownership) {
322 auto process = m_responsible_process.strong_ref();
323 if (!process || process.ptr() != &Process::current()) {
324 dbgln("DisplayConnector::ioctl: {} requires ownership over the device", ioctl_to_stringview(request));
325 return Error::from_errno(EPERM);
326 }
327 }
328
329 switch (request) {
330 case GRAPHICS_IOCTL_SET_RESPONSIBLE: {
331 SpinlockLocker locker(m_responsible_process_lock);
332 auto process = m_responsible_process.strong_ref();
333 // Note: If there's already a process being responsible, just return an error.
334 // We could technically return 0 if the requesting process was already
335 // set to be responsible for this DisplayConnector, but it services
336 // no good purpose and should be considered a bug if this happens anyway.
337 if (process)
338 return Error::from_errno(EPERM);
339 m_responsible_process = Process::current();
340 return {};
341 }
342 case GRAPHICS_IOCTL_UNSET_RESPONSIBLE: {
343 SpinlockLocker locker(m_responsible_process_lock);
344 auto process = m_responsible_process.strong_ref();
345 if (!process)
346 return Error::from_errno(ESRCH);
347 if (process.ptr() != &Process::current())
348 return Error::from_errno(EPERM);
349 m_responsible_process.clear();
350 return {};
351 }
352 case GRAPHICS_IOCTL_GET_PROPERTIES: {
353 VERIFY(m_shared_framebuffer_vmobject);
354 auto user_properties = static_ptr_cast<GraphicsConnectorProperties*>(arg);
355 GraphicsConnectorProperties properties {};
356 properties.flushing_support = flush_support();
357 properties.doublebuffer_support = double_framebuffering_capable();
358 properties.partial_flushing_support = partial_flush_support();
359 properties.refresh_rate_support = refresh_rate_support();
360 properties.max_buffer_bytes = m_shared_framebuffer_vmobject->size();
361
362 return copy_to_user(user_properties, &properties);
363 }
364 case GRAPHICS_IOCTL_GET_HEAD_MODE_SETTING: {
365 auto user_head_mode_setting = static_ptr_cast<GraphicsHeadModeSetting*>(arg);
366 GraphicsHeadModeSetting head_mode_setting {};
367 TRY(copy_from_user(&head_mode_setting, user_head_mode_setting));
368 {
369 SpinlockLocker control_locker(m_control_lock);
370 head_mode_setting.horizontal_stride = m_current_mode_setting.horizontal_stride;
371 head_mode_setting.pixel_clock_in_khz = m_current_mode_setting.pixel_clock_in_khz;
372 head_mode_setting.horizontal_active = m_current_mode_setting.horizontal_active;
373 head_mode_setting.horizontal_front_porch_pixels = m_current_mode_setting.horizontal_front_porch_pixels;
374 head_mode_setting.horizontal_sync_time_pixels = m_current_mode_setting.horizontal_sync_time_pixels;
375 head_mode_setting.horizontal_blank_pixels = m_current_mode_setting.horizontal_blank_pixels;
376 head_mode_setting.vertical_active = m_current_mode_setting.vertical_active;
377 head_mode_setting.vertical_front_porch_lines = m_current_mode_setting.vertical_front_porch_lines;
378 head_mode_setting.vertical_sync_time_lines = m_current_mode_setting.vertical_sync_time_lines;
379 head_mode_setting.vertical_blank_lines = m_current_mode_setting.vertical_blank_lines;
380 head_mode_setting.horizontal_offset = m_current_mode_setting.horizontal_offset;
381 head_mode_setting.vertical_offset = m_current_mode_setting.vertical_offset;
382 }
383 return copy_to_user(user_head_mode_setting, &head_mode_setting);
384 }
385 case GRAPHICS_IOCTL_SET_HEAD_MODE_SETTING: {
386 auto user_mode_setting = static_ptr_cast<GraphicsHeadModeSetting const*>(arg);
387 auto head_mode_setting = TRY(copy_typed_from_user(user_mode_setting));
388
389 if (head_mode_setting.horizontal_stride < 0)
390 return Error::from_errno(EINVAL);
391 if (head_mode_setting.pixel_clock_in_khz < 0)
392 return Error::from_errno(EINVAL);
393 if (head_mode_setting.horizontal_active < 0)
394 return Error::from_errno(EINVAL);
395 if (head_mode_setting.horizontal_front_porch_pixels < 0)
396 return Error::from_errno(EINVAL);
397 if (head_mode_setting.horizontal_sync_time_pixels < 0)
398 return Error::from_errno(EINVAL);
399 if (head_mode_setting.horizontal_blank_pixels < 0)
400 return Error::from_errno(EINVAL);
401 if (head_mode_setting.vertical_active < 0)
402 return Error::from_errno(EINVAL);
403 if (head_mode_setting.vertical_front_porch_lines < 0)
404 return Error::from_errno(EINVAL);
405 if (head_mode_setting.vertical_sync_time_lines < 0)
406 return Error::from_errno(EINVAL);
407 if (head_mode_setting.vertical_blank_lines < 0)
408 return Error::from_errno(EINVAL);
409 if (head_mode_setting.horizontal_offset < 0)
410 return Error::from_errno(EINVAL);
411 if (head_mode_setting.vertical_offset < 0)
412 return Error::from_errno(EINVAL);
413 {
414 SpinlockLocker control_locker(m_control_lock);
415 ModeSetting requested_mode_setting;
416 requested_mode_setting.horizontal_stride = 0;
417 requested_mode_setting.pixel_clock_in_khz = head_mode_setting.pixel_clock_in_khz;
418 requested_mode_setting.horizontal_active = head_mode_setting.horizontal_active;
419 requested_mode_setting.horizontal_front_porch_pixels = head_mode_setting.horizontal_front_porch_pixels;
420 requested_mode_setting.horizontal_sync_time_pixels = head_mode_setting.horizontal_sync_time_pixels;
421 requested_mode_setting.horizontal_blank_pixels = head_mode_setting.horizontal_blank_pixels;
422 requested_mode_setting.vertical_active = head_mode_setting.vertical_active;
423 requested_mode_setting.vertical_front_porch_lines = head_mode_setting.vertical_front_porch_lines;
424 requested_mode_setting.vertical_sync_time_lines = head_mode_setting.vertical_sync_time_lines;
425 requested_mode_setting.vertical_blank_lines = head_mode_setting.vertical_blank_lines;
426 requested_mode_setting.horizontal_offset = head_mode_setting.horizontal_offset;
427 requested_mode_setting.vertical_offset = head_mode_setting.vertical_offset;
428
429 TRY(set_mode_setting(requested_mode_setting));
430 }
431 return {};
432 }
433
434 case GRAPHICS_IOCTL_SET_SAFE_HEAD_MODE_SETTING: {
435 SpinlockLocker control_locker(m_control_lock);
436 TRY(set_safe_mode_setting());
437 return {};
438 }
439
440 case GRAPHICS_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER: {
441 // FIXME: We silently ignore the request if we are in console mode.
442 // WindowServer is not ready yet to handle errors such as EBUSY currently.
443 SpinlockLocker control_locker(m_control_lock);
444 if (console_mode()) {
445 return {};
446 }
447
448 auto user_head_vertical_buffer_offset = static_ptr_cast<GraphicsHeadVerticalOffset const*>(arg);
449 auto head_vertical_buffer_offset = TRY(copy_typed_from_user(user_head_vertical_buffer_offset));
450
451 SpinlockLocker locker(m_modeset_lock);
452
453 if (head_vertical_buffer_offset.offsetted < 0 || head_vertical_buffer_offset.offsetted > 1)
454 return Error::from_errno(EINVAL);
455 TRY(set_y_offset(head_vertical_buffer_offset.offsetted == 0 ? 0 : m_current_mode_setting.vertical_active));
456 if (head_vertical_buffer_offset.offsetted == 0)
457 m_vertical_offsetted = false;
458 else
459 m_vertical_offsetted = true;
460 return {};
461 }
462 case GRAPHICS_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER: {
463 auto user_head_vertical_buffer_offset = static_ptr_cast<GraphicsHeadVerticalOffset*>(arg);
464 GraphicsHeadVerticalOffset head_vertical_buffer_offset {};
465 TRY(copy_from_user(&head_vertical_buffer_offset, user_head_vertical_buffer_offset));
466
467 head_vertical_buffer_offset.offsetted = m_vertical_offsetted;
468 return copy_to_user(user_head_vertical_buffer_offset, &head_vertical_buffer_offset);
469 }
470 case GRAPHICS_IOCTL_FLUSH_HEAD_BUFFERS: {
471 if (console_mode())
472 return {};
473 if (!partial_flush_support())
474 return Error::from_errno(ENOTSUP);
475 MutexLocker locker(m_flushing_lock);
476 auto user_flush_rects = static_ptr_cast<FBFlushRects const*>(arg);
477 auto flush_rects = TRY(copy_typed_from_user(user_flush_rects));
478 if (Checked<unsigned>::multiplication_would_overflow(flush_rects.count, sizeof(FBRect)))
479 return Error::from_errno(EFAULT);
480 if (flush_rects.count > 0) {
481 for (unsigned i = 0; i < flush_rects.count; i++) {
482 FBRect user_dirty_rect;
483 TRY(copy_from_user(&user_dirty_rect, &flush_rects.rects[i]));
484 {
485 SpinlockLocker control_locker(m_control_lock);
486 if (console_mode()) {
487 return {};
488 }
489 TRY(flush_rectangle(flush_rects.buffer_index, user_dirty_rect));
490 }
491 }
492 }
493 return {};
494 };
495 case GRAPHICS_IOCTL_FLUSH_HEAD: {
496 // FIXME: We silently ignore the request if we are in console mode.
497 // WindowServer is not ready yet to handle errors such as EBUSY currently.
498 MutexLocker locker(m_flushing_lock);
499 SpinlockLocker control_locker(m_control_lock);
500 if (console_mode()) {
501 return {};
502 }
503
504 if (!flush_support())
505 return Error::from_errno(ENOTSUP);
506
507 TRY(flush_first_surface());
508 return {};
509 }
510 }
511 // Note: We already verify that the IOCTL is supported and not unknown in
512 // the call to the ioctl_requires_ownership method, so if we reached this
513 // section of the code, this is bug.
514 VERIFY_NOT_REACHED();
515}
516
517}