Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, the SerenityOS developers.
3 * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "HardwareScreenBackend.h"
9#include "ScreenBackend.h"
10#include <AK/Try.h>
11#include <Kernel/API/Graphics.h>
12#include <LibCore/System.h>
13#include <fcntl.h>
14#include <stdio.h>
15#include <sys/mman.h>
16#include <unistd.h>
17
18namespace WindowServer {
19
20HardwareScreenBackend::HardwareScreenBackend(DeprecatedString device)
21 : m_device(move(device))
22{
23}
24
25ErrorOr<void> HardwareScreenBackend::open()
26{
27 m_display_connector_fd = TRY(Core::System::open(m_device, O_RDWR | O_CLOEXEC));
28
29 GraphicsConnectorProperties properties;
30 if (graphics_connector_get_properties(m_display_connector_fd, &properties) < 0)
31 return Error::from_syscall(DeprecatedString::formatted("failed to ioctl {}", m_device), errno);
32
33 m_can_device_flush_buffers = (properties.partial_flushing_support != 0);
34 m_can_device_flush_entire_framebuffer = (properties.flushing_support != 0);
35 m_can_set_head_buffer = (properties.doublebuffer_support != 0);
36 m_max_size_in_bytes = properties.max_buffer_bytes;
37 return {};
38}
39
40HardwareScreenBackend::~HardwareScreenBackend()
41{
42 if (m_display_connector_fd >= 0) {
43 close(m_display_connector_fd);
44 m_display_connector_fd = -1;
45 }
46 if (m_framebuffer) {
47 MUST(Core::System::munmap(m_framebuffer, m_size_in_bytes));
48
49 m_framebuffer = nullptr;
50 m_size_in_bytes = 0;
51 }
52}
53
54ErrorOr<void> HardwareScreenBackend::set_safe_head_mode_setting()
55{
56 auto rc = graphics_connector_set_safe_head_mode_setting(m_display_connector_fd);
57 if (rc != 0) {
58 dbgln("Failed to set backend safe mode setting: aborting");
59 return Error::from_syscall("graphics_connector_set_safe_head_mode_setting"sv, rc);
60 }
61 return {};
62}
63
64ErrorOr<void> HardwareScreenBackend::set_head_mode_setting(GraphicsHeadModeSetting mode_setting)
65{
66 size_t size_in_bytes = 0;
67 if (m_can_set_head_buffer) {
68 size_in_bytes = mode_setting.horizontal_stride * mode_setting.vertical_active * 2;
69 } else {
70 size_in_bytes = mode_setting.horizontal_stride * mode_setting.vertical_active;
71 }
72 VERIFY(size_in_bytes != 0);
73 if (m_max_size_in_bytes < size_in_bytes)
74 return Error::from_errno(EOVERFLOW);
75
76 GraphicsHeadModeSetting requested_mode_setting = mode_setting;
77 auto rc = graphics_connector_set_head_mode_setting(m_display_connector_fd, &requested_mode_setting);
78 if (rc != 0) {
79 dbgln("Failed to set backend mode setting: falling back to safe resolution");
80 rc = graphics_connector_set_safe_head_mode_setting(m_display_connector_fd);
81 if (rc != 0) {
82 dbgln("Failed to set backend safe mode setting: aborting");
83 return Error::from_syscall("graphics_connector_set_safe_head_mode_setting"sv, rc);
84 }
85 dbgln("Failed to set backend mode setting: falling back to safe resolution - success.");
86 }
87
88 return {};
89}
90
91ErrorOr<void> HardwareScreenBackend::unmap_framebuffer()
92{
93 if (m_framebuffer) {
94 size_t previous_size_in_bytes = m_size_in_bytes;
95 return Core::System::munmap(m_framebuffer, previous_size_in_bytes);
96 }
97 return {};
98}
99
100ErrorOr<void> HardwareScreenBackend::map_framebuffer()
101{
102 GraphicsHeadModeSetting mode_setting {};
103 memset(&mode_setting, 0, sizeof(GraphicsHeadModeSetting));
104 int rc = graphics_connector_get_head_mode_setting(m_display_connector_fd, &mode_setting);
105 if (rc != 0) {
106 return Error::from_syscall("graphics_connector_get_head_mode_setting"sv, rc);
107 }
108 if (m_can_set_head_buffer) {
109 m_size_in_bytes = mode_setting.horizontal_stride * mode_setting.vertical_active * 2;
110 } else {
111 m_size_in_bytes = mode_setting.horizontal_stride * mode_setting.vertical_active;
112 }
113
114 if (m_max_size_in_bytes < m_size_in_bytes)
115 return Error::from_errno(EOVERFLOW);
116 m_framebuffer = (Gfx::ARGB32*)TRY(Core::System::mmap(nullptr, m_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, m_display_connector_fd, 0));
117
118 if (m_can_set_head_buffer) {
119 // Note: fall back to assuming the second buffer starts right after the last line of the first
120 // Note: for now, this calculation works quite well, so need to defer it to another function
121 // that does ioctl to figure out the correct offset. If a Framebuffer device ever happens to
122 // to set the second buffer at different location than this, we might need to consider bringing
123 // back a function with ioctl to check this.
124 m_back_buffer_offset = static_cast<size_t>(mode_setting.horizontal_stride) * mode_setting.vertical_active;
125 } else {
126 m_back_buffer_offset = 0;
127 }
128
129 return {};
130}
131
132ErrorOr<GraphicsHeadModeSetting> HardwareScreenBackend::get_head_mode_setting()
133{
134 GraphicsHeadModeSetting mode_setting {};
135 memset(&mode_setting, 0, sizeof(GraphicsHeadModeSetting));
136 int rc = graphics_connector_get_head_mode_setting(m_display_connector_fd, &mode_setting);
137 if (rc != 0) {
138 return Error::from_syscall("graphics_connector_get_head_mode_setting"sv, rc);
139 }
140 m_pitch = mode_setting.horizontal_stride;
141 return mode_setting;
142}
143
144void HardwareScreenBackend::set_head_buffer(int head_index)
145{
146 VERIFY(m_can_set_head_buffer);
147 VERIFY(head_index <= 1 && head_index >= 0);
148 GraphicsHeadVerticalOffset offset { 0, 0 };
149 if (head_index == 1)
150 offset.offsetted = 1;
151 int rc = fb_set_head_vertical_offset_buffer(m_display_connector_fd, &offset);
152 VERIFY(rc == 0);
153}
154
155ErrorOr<void> HardwareScreenBackend::flush_framebuffer_rects(int buffer_index, ReadonlySpan<FBRect> flush_rects)
156{
157 int rc = fb_flush_buffers(m_display_connector_fd, buffer_index, flush_rects.data(), (unsigned)flush_rects.size());
158 if (rc == -ENOTSUP)
159 m_can_device_flush_buffers = false;
160 else if (rc != 0)
161 return Error::from_syscall("fb_flush_buffers"sv, rc);
162 return {};
163}
164
165ErrorOr<void> HardwareScreenBackend::flush_framebuffer()
166{
167 int rc = fb_flush_head(m_display_connector_fd);
168 if (rc == -ENOTSUP)
169 m_can_device_flush_entire_framebuffer = false;
170 else if (rc != 0)
171 return Error::from_syscall("fb_flush_head"sv, rc);
172 return {};
173}
174}