Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <Kernel/Devices/BXVGADevice.h>
28#include <Kernel/PCI/Access.h>
29#include <Kernel/Process.h>
30#include <Kernel/VM/AnonymousVMObject.h>
31#include <Kernel/VM/MemoryManager.h>
32#include <LibBareMetal/IO.h>
33#include <LibC/errno_numbers.h>
34#include <LibC/sys/ioctl_numbers.h>
35
36namespace Kernel {
37
38#define MAX_RESOLUTION_WIDTH 4096
39#define MAX_RESOLUTION_HEIGHT 2160
40
41#define VBE_DISPI_IOPORT_INDEX 0x01CE
42#define VBE_DISPI_IOPORT_DATA 0x01CF
43
44#define VBE_DISPI_INDEX_ID 0x0
45#define VBE_DISPI_INDEX_XRES 0x1
46#define VBE_DISPI_INDEX_YRES 0x2
47#define VBE_DISPI_INDEX_BPP 0x3
48#define VBE_DISPI_INDEX_ENABLE 0x4
49#define VBE_DISPI_INDEX_BANK 0x5
50#define VBE_DISPI_INDEX_VIRT_WIDTH 0x6
51#define VBE_DISPI_INDEX_VIRT_HEIGHT 0x7
52#define VBE_DISPI_INDEX_X_OFFSET 0x8
53#define VBE_DISPI_INDEX_Y_OFFSET 0x9
54#define VBE_DISPI_DISABLED 0x00
55#define VBE_DISPI_ENABLED 0x01
56#define VBE_DISPI_LFB_ENABLED 0x40
57
58static BXVGADevice* s_the;
59
60BXVGADevice& BXVGADevice::the()
61{
62 return *s_the;
63}
64
65BXVGADevice::BXVGADevice()
66 : BlockDevice(29, 0)
67
68{
69 s_the = this;
70 m_framebuffer_address = PhysicalAddress(find_framebuffer_address());
71 set_safe_resolution();
72}
73
74void BXVGADevice::set_safe_resolution()
75{
76 m_framebuffer_width = 1024;
77 m_framebuffer_height = 768;
78 m_framebuffer_pitch = m_framebuffer_width * sizeof(u32);
79 set_resolution(m_framebuffer_width, m_framebuffer_height);
80}
81
82void BXVGADevice::set_register(u16 index, u16 data)
83{
84 IO::out16(VBE_DISPI_IOPORT_INDEX, index);
85 IO::out16(VBE_DISPI_IOPORT_DATA, data);
86}
87
88u16 BXVGADevice::get_register(u16 index)
89{
90 IO::out16(VBE_DISPI_IOPORT_INDEX, index);
91 return IO::in16(VBE_DISPI_IOPORT_DATA);
92}
93
94void BXVGADevice::revert_resolution()
95{
96 set_resolution_registers(m_framebuffer_width, m_framebuffer_height);
97 ASSERT(validate_setup_resolution(m_framebuffer_width, m_framebuffer_height));
98}
99
100void BXVGADevice::set_resolution_registers(int width, int height)
101{
102#ifdef BXVGA_DEBUG
103 dbg() << "BXVGADevice resolution registers set to - " << width << "x" << height;
104#endif
105 set_register(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED);
106 set_register(VBE_DISPI_INDEX_XRES, (u16)width);
107 set_register(VBE_DISPI_INDEX_YRES, (u16)height);
108 set_register(VBE_DISPI_INDEX_VIRT_WIDTH, (u16)width);
109 set_register(VBE_DISPI_INDEX_VIRT_HEIGHT, (u16)height * 2);
110 set_register(VBE_DISPI_INDEX_BPP, 32);
111 set_register(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED);
112 set_register(VBE_DISPI_INDEX_BANK, 0);
113}
114
115bool BXVGADevice::test_resolution(int width, int height)
116{
117#ifdef BXVGA_DEBUG
118 dbg() << "BXVGADevice resolution test - " << width << "x" << height;
119#endif
120 set_resolution_registers(width, height);
121 bool resolution_changed = validate_setup_resolution(width, height);
122 revert_resolution();
123 return resolution_changed;
124}
125bool BXVGADevice::set_resolution(int width, int height)
126{
127 if (!test_resolution(width, height))
128 return false;
129
130 set_resolution_registers(width, height);
131 dbg() << "BXVGADevice resolution set to " << width << "x" << height << " (pitch=" << m_framebuffer_pitch << ")";
132
133 m_framebuffer_width = width;
134 m_framebuffer_height = height;
135 m_framebuffer_pitch = width * sizeof(u32);
136 return true;
137}
138
139bool BXVGADevice::validate_setup_resolution(int width, int height)
140{
141 if ((u16)width != get_register(VBE_DISPI_INDEX_XRES) || (u16)height != get_register(VBE_DISPI_INDEX_YRES)) {
142 return false;
143 }
144 return true;
145}
146
147void BXVGADevice::set_y_offset(int y_offset)
148{
149 ASSERT(y_offset == 0 || y_offset == m_framebuffer_height);
150 m_y_offset = y_offset;
151 set_register(VBE_DISPI_INDEX_Y_OFFSET, (u16)y_offset);
152}
153
154u32 BXVGADevice::find_framebuffer_address()
155{
156 // NOTE: The QEMU card has the same PCI ID as the Bochs one.
157 static const PCI::ID bochs_vga_id = { 0x1234, 0x1111 };
158 static const PCI::ID virtualbox_vga_id = { 0x80ee, 0xbeef };
159 u32 framebuffer_address = 0;
160 PCI::enumerate_all([&framebuffer_address](const PCI::Address& address, PCI::ID id) {
161 if (id == bochs_vga_id || id == virtualbox_vga_id) {
162 framebuffer_address = PCI::get_BAR0(address) & 0xfffffff0;
163 klog() << "BXVGA: framebuffer @ " << PhysicalAddress(framebuffer_address);
164 }
165 });
166 return framebuffer_address;
167}
168
169KResultOr<Region*> BXVGADevice::mmap(Process& process, FileDescription&, VirtualAddress preferred_vaddr, size_t offset, size_t size, int prot, bool shared)
170{
171 REQUIRE_PROMISE(video);
172 if (!shared)
173 return KResult(-ENODEV);
174 ASSERT(offset == 0);
175 ASSERT(size == framebuffer_size_in_bytes());
176 auto vmobject = AnonymousVMObject::create_for_physical_range(m_framebuffer_address, framebuffer_size_in_bytes());
177 if (!vmobject)
178 return KResult(-ENOMEM);
179 auto* region = process.allocate_region_with_vmobject(
180 preferred_vaddr,
181 framebuffer_size_in_bytes(),
182 vmobject.release_nonnull(),
183 0,
184 "BXVGA Framebuffer",
185 prot);
186 dbg() << "BXVGADevice: mmap with size " << region->size() << " at " << region->vaddr();
187 ASSERT(region);
188 return region;
189}
190
191int BXVGADevice::ioctl(FileDescription&, unsigned request, unsigned arg)
192{
193 REQUIRE_PROMISE(video);
194 switch (request) {
195 case FB_IOCTL_GET_SIZE_IN_BYTES: {
196 auto* out = (size_t*)arg;
197 if (!Process::current->validate_write_typed(out))
198 return -EFAULT;
199 *out = framebuffer_size_in_bytes();
200 return 0;
201 }
202 case FB_IOCTL_GET_BUFFER: {
203 auto* index = (int*)arg;
204 if (!Process::current->validate_write_typed(index))
205 return -EFAULT;
206 *index = m_y_offset == 0 ? 0 : 1;
207 return 0;
208 }
209 case FB_IOCTL_SET_BUFFER: {
210 if (arg != 0 && arg != 1)
211 return -EINVAL;
212 set_y_offset(arg == 0 ? 0 : m_framebuffer_height);
213 return 0;
214 }
215 case FB_IOCTL_GET_RESOLUTION: {
216 auto* resolution = (FBResolution*)arg;
217 if (!Process::current->validate_write_typed(resolution))
218 return -EFAULT;
219 resolution->pitch = m_framebuffer_pitch;
220 resolution->width = m_framebuffer_width;
221 resolution->height = m_framebuffer_height;
222 return 0;
223 }
224 case FB_IOCTL_SET_RESOLUTION: {
225 auto* resolution = (FBResolution*)arg;
226 if (!Process::current->validate_read_typed(resolution) || !Process::current->validate_write_typed(resolution))
227 return -EFAULT;
228 if (resolution->width > MAX_RESOLUTION_WIDTH || resolution->height > MAX_RESOLUTION_HEIGHT)
229 return -EINVAL;
230 if (!set_resolution(resolution->width, resolution->height)) {
231#ifdef BXVGA_DEBUG
232 dbg() << "Reverting Resolution: [" << m_framebuffer_width << "x" << m_framebuffer_height << "]";
233#endif
234 resolution->pitch = m_framebuffer_pitch;
235 resolution->width = m_framebuffer_width;
236 resolution->height = m_framebuffer_height;
237 return -EINVAL;
238 }
239#ifdef BXVGA_DEBUG
240 dbg() << "New resolution: [" << m_framebuffer_width << "x" << m_framebuffer_height << "]";
241#endif
242 resolution->pitch = m_framebuffer_pitch;
243 resolution->width = m_framebuffer_width;
244 resolution->height = m_framebuffer_height;
245 return 0;
246 }
247 default:
248 return -EINVAL;
249 };
250}
251
252}