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 s_the = this;
69 m_framebuffer_address = PhysicalAddress(find_framebuffer_address());
70}
71
72void BXVGADevice::set_register(u16 index, u16 data)
73{
74 IO::out16(VBE_DISPI_IOPORT_INDEX, index);
75 IO::out16(VBE_DISPI_IOPORT_DATA, data);
76}
77
78void BXVGADevice::set_resolution(int width, int height)
79{
80 m_framebuffer_pitch = width * sizeof(u32);
81 m_framebuffer_width = width;
82 m_framebuffer_height = height;
83
84 set_register(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED);
85 set_register(VBE_DISPI_INDEX_XRES, (u16)width);
86 set_register(VBE_DISPI_INDEX_YRES, (u16)height);
87 set_register(VBE_DISPI_INDEX_VIRT_WIDTH, (u16)width);
88 set_register(VBE_DISPI_INDEX_VIRT_HEIGHT, (u16)height * 2);
89 set_register(VBE_DISPI_INDEX_BPP, 32);
90 set_register(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED);
91 set_register(VBE_DISPI_INDEX_BANK, 0);
92
93 dbg() << "BXVGADevice resolution set to " << m_framebuffer_width << "x" << m_framebuffer_height << " (pitch=" << m_framebuffer_pitch << ")";
94}
95
96void BXVGADevice::set_y_offset(int y_offset)
97{
98 ASSERT(y_offset == 0 || y_offset == m_framebuffer_height);
99 m_y_offset = y_offset;
100 set_register(VBE_DISPI_INDEX_Y_OFFSET, (u16)y_offset);
101}
102
103u32 BXVGADevice::find_framebuffer_address()
104{
105 // NOTE: The QEMU card has the same PCI ID as the Bochs one.
106 static const PCI::ID bochs_vga_id = { 0x1234, 0x1111 };
107 static const PCI::ID virtualbox_vga_id = { 0x80ee, 0xbeef };
108 u32 framebuffer_address = 0;
109 PCI::enumerate_all([&framebuffer_address](const PCI::Address& address, PCI::ID id) {
110 if (id == bochs_vga_id || id == virtualbox_vga_id) {
111 framebuffer_address = PCI::get_BAR0(address) & 0xfffffff0;
112 kprintf("BXVGA: framebuffer @ P%x\n", framebuffer_address);
113 }
114 });
115 return framebuffer_address;
116}
117
118KResultOr<Region*> BXVGADevice::mmap(Process& process, FileDescription&, VirtualAddress preferred_vaddr, size_t offset, size_t size, int prot)
119{
120 REQUIRE_PROMISE(video);
121 ASSERT(offset == 0);
122 ASSERT(size == framebuffer_size_in_bytes());
123 auto vmobject = AnonymousVMObject::create_for_physical_range(m_framebuffer_address, framebuffer_size_in_bytes());
124 if (!vmobject)
125 return KResult(-ENOMEM);
126 auto* region = process.allocate_region_with_vmobject(
127 preferred_vaddr,
128 framebuffer_size_in_bytes(),
129 vmobject.release_nonnull(),
130 0,
131 "BXVGA Framebuffer",
132 prot);
133 dbg() << "BXVGADevice: mmap with size " << region->size() << " at " << region->vaddr();
134 ASSERT(region);
135 return region;
136}
137
138int BXVGADevice::ioctl(FileDescription&, unsigned request, unsigned arg)
139{
140 REQUIRE_PROMISE(video);
141 switch (request) {
142 case FB_IOCTL_GET_SIZE_IN_BYTES: {
143 auto* out = (size_t*)arg;
144 if (!Process::current->validate_write_typed(out))
145 return -EFAULT;
146 *out = framebuffer_size_in_bytes();
147 return 0;
148 }
149 case FB_IOCTL_GET_BUFFER: {
150 auto* index = (int*)arg;
151 if (!Process::current->validate_write_typed(index))
152 return -EFAULT;
153 *index = m_y_offset == 0 ? 0 : 1;
154 return 0;
155 }
156 case FB_IOCTL_SET_BUFFER: {
157 if (arg != 0 && arg != 1)
158 return -EINVAL;
159 set_y_offset(arg == 0 ? 0 : m_framebuffer_height);
160 return 0;
161 }
162 case FB_IOCTL_GET_RESOLUTION: {
163 auto* resolution = (FBResolution*)arg;
164 if (!Process::current->validate_write_typed(resolution))
165 return -EFAULT;
166 resolution->pitch = m_framebuffer_pitch;
167 resolution->width = m_framebuffer_width;
168 resolution->height = m_framebuffer_height;
169 return 0;
170 }
171 case FB_IOCTL_SET_RESOLUTION: {
172 auto* resolution = (FBResolution*)arg;
173 if (!Process::current->validate_read_typed(resolution) || !Process::current->validate_write_typed(resolution))
174 return -EFAULT;
175 if (resolution->width > MAX_RESOLUTION_WIDTH || resolution->height > MAX_RESOLUTION_HEIGHT)
176 return -EINVAL;
177 set_resolution(resolution->width, resolution->height);
178 resolution->pitch = m_framebuffer_pitch;
179 resolution->width = m_framebuffer_width;
180 resolution->height = m_framebuffer_height;
181 return 0;
182 }
183 default:
184 return -EINVAL;
185 };
186}
187
188}