mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 04:28:13 +00:00
Kernel + WindowServer: Re-define the interface to framebuffer devices
We create a base class called GenericFramebufferDevice, which defines all the virtual functions that must be implemented by a FramebufferDevice. Then, we make the VirtIO FramebufferDevice and other FramebufferDevice implementations inherit from it. The most important consequence of rearranging the classes is that we now have one IOCTL method, so all drivers should be committed to not override the IOCTL method or make their own IOCTLs of FramebufferDevice. All graphical IOCTLs are known to all FramebufferDevices, and it's up to the specific implementation whether to support them or discard them (so we require extensive usage of KResult and KResultOr, together with virtual characteristic functions). As a result, the interface is much cleaner and understandable to read.
This commit is contained in:
parent
78e724a899
commit
8554952690
19 changed files with 673 additions and 350 deletions
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/Checked.h>
|
||||
#include <AK/Try.h>
|
||||
#include <Kernel/Debug.h>
|
||||
#include <Kernel/Devices/DeviceManagement.h>
|
||||
#include <Kernel/Graphics/FramebufferDevice.h>
|
||||
|
@ -16,14 +17,11 @@
|
|||
#include <LibC/errno_numbers.h>
|
||||
#include <LibC/sys/ioctl_numbers.h>
|
||||
|
||||
#define MAX_RESOLUTION_WIDTH 4096
|
||||
#define MAX_RESOLUTION_HEIGHT 2160
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
NonnullRefPtr<FramebufferDevice> FramebufferDevice::create(const GenericGraphicsAdapter& adapter, size_t output_port_index, PhysicalAddress paddr, size_t width, size_t height, size_t pitch)
|
||||
NonnullRefPtr<FramebufferDevice> FramebufferDevice::create(const GenericGraphicsAdapter& adapter, PhysicalAddress paddr, size_t width, size_t height, size_t pitch)
|
||||
{
|
||||
auto framebuffer_device_or_error = DeviceManagement::try_create_device<FramebufferDevice>(adapter, output_port_index, paddr, width, height, pitch);
|
||||
auto framebuffer_device_or_error = DeviceManagement::try_create_device<FramebufferDevice>(adapter, paddr, width, height, pitch);
|
||||
// FIXME: Find a way to propagate errors
|
||||
VERIFY(!framebuffer_device_or_error.is_error());
|
||||
return framebuffer_device_or_error.release_value();
|
||||
|
@ -37,14 +35,15 @@ KResultOr<Memory::Region*> FramebufferDevice::mmap(Process& process, OpenFileDes
|
|||
return ENODEV;
|
||||
if (offset != 0)
|
||||
return ENXIO;
|
||||
if (range.size() != Memory::page_round_up(framebuffer_size_in_bytes()))
|
||||
auto framebuffer_length = TRY(buffer_length(0));
|
||||
if (range.size() != Memory::page_round_up(framebuffer_length))
|
||||
return EOVERFLOW;
|
||||
|
||||
m_userspace_real_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_for_physical_range(m_framebuffer_address, Memory::page_round_up(framebuffer_size_in_bytes())));
|
||||
m_real_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_for_physical_range(m_framebuffer_address, Memory::page_round_up(framebuffer_size_in_bytes())));
|
||||
m_swapped_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_with_size(Memory::page_round_up(framebuffer_size_in_bytes()), AllocationStrategy::AllocateNow));
|
||||
m_real_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(*m_real_framebuffer_vmobject, Memory::page_round_up(framebuffer_size_in_bytes()), "Framebuffer", Memory::Region::Access::ReadWrite));
|
||||
m_swapped_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(*m_swapped_framebuffer_vmobject, Memory::page_round_up(framebuffer_size_in_bytes()), "Framebuffer Swap (Blank)", Memory::Region::Access::ReadWrite));
|
||||
m_userspace_real_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_for_physical_range(m_framebuffer_address, Memory::page_round_up(framebuffer_length)));
|
||||
m_real_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_for_physical_range(m_framebuffer_address, Memory::page_round_up(framebuffer_length)));
|
||||
m_swapped_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_with_size(Memory::page_round_up(framebuffer_length), AllocationStrategy::AllocateNow));
|
||||
m_real_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(*m_real_framebuffer_vmobject, Memory::page_round_up(framebuffer_length), "Framebuffer", Memory::Region::Access::ReadWrite));
|
||||
m_swapped_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(*m_swapped_framebuffer_vmobject, Memory::page_round_up(framebuffer_length), "Framebuffer Swap (Blank)", Memory::Region::Access::ReadWrite));
|
||||
|
||||
RefPtr<Memory::VMObject> chosen_vmobject;
|
||||
if (m_graphical_writes_enabled) {
|
||||
|
@ -67,7 +66,9 @@ void FramebufferDevice::deactivate_writes()
|
|||
SpinlockLocker lock(m_activation_lock);
|
||||
if (!m_userspace_framebuffer_region)
|
||||
return;
|
||||
memcpy(m_swapped_framebuffer_region->vaddr().as_ptr(), m_real_framebuffer_region->vaddr().as_ptr(), Memory::page_round_up(framebuffer_size_in_bytes()));
|
||||
auto framebuffer_length_or_error = buffer_length(0);
|
||||
VERIFY(!framebuffer_length_or_error.is_error());
|
||||
memcpy(m_swapped_framebuffer_region->vaddr().as_ptr(), m_real_framebuffer_region->vaddr().as_ptr(), Memory::page_round_up(framebuffer_length_or_error.release_value()));
|
||||
auto vmobject = m_swapped_framebuffer_vmobject;
|
||||
m_userspace_framebuffer_region->set_vmobject(vmobject.release_nonnull());
|
||||
m_userspace_framebuffer_region->remap();
|
||||
|
@ -81,39 +82,34 @@ void FramebufferDevice::activate_writes()
|
|||
// restore the image we had in the void area
|
||||
// FIXME: if we happen to have multiple Framebuffers that are writing to that location
|
||||
// we will experience glitches...
|
||||
memcpy(m_real_framebuffer_region->vaddr().as_ptr(), m_swapped_framebuffer_region->vaddr().as_ptr(), Memory::page_round_up(framebuffer_size_in_bytes()));
|
||||
auto framebuffer_length_or_error = buffer_length(0);
|
||||
VERIFY(!framebuffer_length_or_error.is_error());
|
||||
|
||||
memcpy(m_real_framebuffer_region->vaddr().as_ptr(), m_swapped_framebuffer_region->vaddr().as_ptr(), Memory::page_round_up(framebuffer_length_or_error.release_value()));
|
||||
auto vmobject = m_userspace_real_framebuffer_vmobject;
|
||||
m_userspace_framebuffer_region->set_vmobject(vmobject.release_nonnull());
|
||||
m_userspace_framebuffer_region->remap();
|
||||
m_graphical_writes_enabled = true;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT KResult FramebufferDevice::initialize()
|
||||
UNMAP_AFTER_INIT KResult FramebufferDevice::try_to_initialize()
|
||||
{
|
||||
// FIXME: Would be nice to be able to unify this with mmap above, but this
|
||||
// function is UNMAP_AFTER_INIT for the time being.
|
||||
m_real_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_for_physical_range(m_framebuffer_address, Memory::page_round_up(framebuffer_size_in_bytes())));
|
||||
m_swapped_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_with_size(Memory::page_round_up(framebuffer_size_in_bytes()), AllocationStrategy::AllocateNow));
|
||||
m_real_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(*m_real_framebuffer_vmobject, Memory::page_round_up(framebuffer_size_in_bytes()), "Framebuffer", Memory::Region::Access::ReadWrite));
|
||||
m_swapped_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(*m_swapped_framebuffer_vmobject, Memory::page_round_up(framebuffer_size_in_bytes()), "Framebuffer Swap (Blank)", Memory::Region::Access::ReadWrite));
|
||||
auto framebuffer_length = TRY(buffer_length(0));
|
||||
m_real_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_for_physical_range(m_framebuffer_address, Memory::page_round_up(framebuffer_length)));
|
||||
m_swapped_framebuffer_vmobject = TRY(Memory::AnonymousVMObject::try_create_with_size(Memory::page_round_up(framebuffer_length), AllocationStrategy::AllocateNow));
|
||||
m_real_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(*m_real_framebuffer_vmobject, Memory::page_round_up(framebuffer_length), "Framebuffer", Memory::Region::Access::ReadWrite));
|
||||
m_swapped_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(*m_swapped_framebuffer_vmobject, Memory::page_round_up(framebuffer_length), "Framebuffer Swap (Blank)", Memory::Region::Access::ReadWrite));
|
||||
return KSuccess;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT FramebufferDevice::FramebufferDevice(const GenericGraphicsAdapter& adapter, size_t output_port_index)
|
||||
: BlockDevice(29, GraphicsManagement::the().allocate_minor_device_number())
|
||||
, m_graphics_adapter(adapter)
|
||||
, m_output_port_index(output_port_index)
|
||||
{
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT FramebufferDevice::FramebufferDevice(const GenericGraphicsAdapter& adapter, size_t output_port_index, PhysicalAddress addr, size_t width, size_t height, size_t pitch)
|
||||
: BlockDevice(29, GraphicsManagement::the().allocate_minor_device_number())
|
||||
, m_graphics_adapter(adapter)
|
||||
UNMAP_AFTER_INIT FramebufferDevice::FramebufferDevice(const GenericGraphicsAdapter& adapter, PhysicalAddress addr, size_t width, size_t height, size_t pitch)
|
||||
: GenericFramebufferDevice(adapter)
|
||||
, m_framebuffer_address(addr)
|
||||
, m_framebuffer_pitch(pitch)
|
||||
, m_framebuffer_width(width)
|
||||
, m_framebuffer_height(height)
|
||||
, m_output_port_index(output_port_index)
|
||||
{
|
||||
VERIFY(!m_framebuffer_address.is_null());
|
||||
VERIFY(m_framebuffer_pitch);
|
||||
|
@ -122,99 +118,125 @@ UNMAP_AFTER_INIT FramebufferDevice::FramebufferDevice(const GenericGraphicsAdapt
|
|||
dbgln("Framebuffer {}: address={}, pitch={}, width={}, height={}", minor(), addr, pitch, width, height);
|
||||
}
|
||||
|
||||
size_t FramebufferDevice::framebuffer_size_in_bytes() const
|
||||
KResultOr<size_t> FramebufferDevice::buffer_length(size_t head) const
|
||||
{
|
||||
if (m_graphics_adapter->double_framebuffering_capable())
|
||||
// Note: This FramebufferDevice class doesn't support multihead setup.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally have a value different than 0, assert.
|
||||
VERIFY(head == 0);
|
||||
MutexLocker locker(m_resolution_lock);
|
||||
auto adapter = m_graphics_adapter.strong_ref();
|
||||
if (!adapter)
|
||||
return KResult(EIO);
|
||||
if (adapter->double_framebuffering_capable())
|
||||
return m_framebuffer_pitch * m_framebuffer_height * 2;
|
||||
return m_framebuffer_pitch * m_framebuffer_height;
|
||||
}
|
||||
|
||||
KResult FramebufferDevice::ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg)
|
||||
KResultOr<size_t> FramebufferDevice::pitch(size_t head) const
|
||||
{
|
||||
REQUIRE_PROMISE(video);
|
||||
switch (request) {
|
||||
case FB_IOCTL_GET_SIZE_IN_BYTES: {
|
||||
auto user_size = static_ptr_cast<size_t*>(arg);
|
||||
size_t value = framebuffer_size_in_bytes();
|
||||
return copy_to_user(user_size, &value);
|
||||
}
|
||||
case FB_IOCTL_GET_BUFFER: {
|
||||
auto user_index = static_ptr_cast<int*>(arg);
|
||||
int value = m_y_offset == 0 ? 0 : 1;
|
||||
TRY(copy_to_user(user_index, &value));
|
||||
if (!m_graphics_adapter->double_framebuffering_capable())
|
||||
return ENOTIMPL;
|
||||
return KSuccess;
|
||||
}
|
||||
case FB_IOCTL_SET_BUFFER: {
|
||||
auto buffer = static_cast<int>(arg.ptr());
|
||||
if (buffer != 0 && buffer != 1)
|
||||
return EINVAL;
|
||||
if (!m_graphics_adapter->double_framebuffering_capable())
|
||||
return ENOTIMPL;
|
||||
m_graphics_adapter->set_y_offset(m_output_port_index, buffer == 0 ? 0 : m_framebuffer_height);
|
||||
return KSuccess;
|
||||
}
|
||||
case FB_IOCTL_GET_RESOLUTION: {
|
||||
auto user_resolution = static_ptr_cast<FBResolution*>(arg);
|
||||
FBResolution resolution {};
|
||||
resolution.pitch = m_framebuffer_pitch;
|
||||
resolution.width = m_framebuffer_width;
|
||||
resolution.height = m_framebuffer_height;
|
||||
return copy_to_user(user_resolution, &resolution);
|
||||
}
|
||||
case FB_IOCTL_SET_RESOLUTION: {
|
||||
auto user_resolution = static_ptr_cast<FBResolution*>(arg);
|
||||
FBResolution resolution;
|
||||
TRY(copy_from_user(&resolution, user_resolution));
|
||||
if (resolution.width > MAX_RESOLUTION_WIDTH || resolution.height > MAX_RESOLUTION_HEIGHT)
|
||||
return EINVAL;
|
||||
// Note: This FramebufferDevice class doesn't support multihead setup.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally have a value different than 0, assert.
|
||||
VERIFY(head == 0);
|
||||
MutexLocker locker(m_resolution_lock);
|
||||
return m_framebuffer_pitch;
|
||||
}
|
||||
KResultOr<size_t> FramebufferDevice::height(size_t head) const
|
||||
{
|
||||
// Note: This FramebufferDevice class doesn't support multihead setup.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally have a value different than 0, assert.
|
||||
VERIFY(head == 0);
|
||||
MutexLocker locker(m_resolution_lock);
|
||||
return m_framebuffer_height;
|
||||
}
|
||||
KResultOr<size_t> FramebufferDevice::width(size_t head) const
|
||||
{
|
||||
// Note: This FramebufferDevice class doesn't support multihead setup.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally have a value different than 0, assert.
|
||||
VERIFY(head == 0);
|
||||
MutexLocker locker(m_resolution_lock);
|
||||
return m_framebuffer_width;
|
||||
}
|
||||
KResultOr<size_t> FramebufferDevice::vertical_offset(size_t head) const
|
||||
{
|
||||
// Note: This FramebufferDevice class doesn't support multihead setup.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally have a value different than 0, assert.
|
||||
VERIFY(head == 0);
|
||||
MutexLocker locker(m_buffer_offset_lock);
|
||||
return m_y_offset;
|
||||
}
|
||||
KResultOr<bool> FramebufferDevice::vertical_offseted(size_t head) const
|
||||
{
|
||||
// Note: This FramebufferDevice class doesn't support multihead setup.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally have a value different than 0, assert.
|
||||
VERIFY(head == 0);
|
||||
MutexLocker locker(m_buffer_offset_lock);
|
||||
return m_y_offset == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
if (!m_graphics_adapter->modesetting_capable()) {
|
||||
resolution.pitch = m_framebuffer_pitch;
|
||||
resolution.width = m_framebuffer_width;
|
||||
resolution.height = m_framebuffer_height;
|
||||
TRY(copy_to_user(user_resolution, &resolution));
|
||||
return ENOTIMPL;
|
||||
KResult FramebufferDevice::set_head_resolution(size_t head, size_t width, size_t height, size_t)
|
||||
{
|
||||
// Note: This FramebufferDevice class doesn't support multihead setup.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally have a value different than 0, assert.
|
||||
VERIFY(head == 0);
|
||||
MutexLocker buffer_offset_locker(m_buffer_offset_lock);
|
||||
MutexLocker resolution_locker(m_resolution_lock);
|
||||
auto adapter = m_graphics_adapter.strong_ref();
|
||||
if (!adapter)
|
||||
return KResult(EIO);
|
||||
auto result = adapter->try_to_set_resolution(0, width, height);
|
||||
// FIXME: Find a better way to return here a KResult.
|
||||
if (!result)
|
||||
return KResult(ENOTSUP);
|
||||
m_framebuffer_width = width;
|
||||
m_framebuffer_height = height;
|
||||
m_framebuffer_pitch = width * sizeof(u32);
|
||||
return KSuccess;
|
||||
}
|
||||
KResult FramebufferDevice::set_head_buffer(size_t head, bool second_buffer)
|
||||
{
|
||||
// Note: This FramebufferDevice class doesn't support multihead setup.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally have a value different than 0, assert.
|
||||
VERIFY(head == 0);
|
||||
MutexLocker locker(m_buffer_offset_lock);
|
||||
auto adapter = m_graphics_adapter.strong_ref();
|
||||
if (!adapter)
|
||||
return KResult(EIO);
|
||||
if (second_buffer) {
|
||||
if (!adapter->set_y_offset(0, m_framebuffer_height)) {
|
||||
// FIXME: Find a better KResult here.
|
||||
return KResult(ENOTSUP);
|
||||
}
|
||||
|
||||
if (!m_graphics_adapter->try_to_set_resolution(m_output_port_index, resolution.width, resolution.height)) {
|
||||
m_framebuffer_pitch = m_framebuffer_width * sizeof(u32);
|
||||
dbgln_if(FRAMEBUFFER_DEVICE_DEBUG, "Reverting resolution: [{}x{}]", m_framebuffer_width, m_framebuffer_height);
|
||||
// Note: We try to revert everything back, and if it doesn't work, just assert.
|
||||
if (!m_graphics_adapter->try_to_set_resolution(m_output_port_index, m_framebuffer_width, m_framebuffer_height)) {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
resolution.pitch = m_framebuffer_pitch;
|
||||
resolution.width = m_framebuffer_width;
|
||||
resolution.height = m_framebuffer_height;
|
||||
TRY(copy_to_user(user_resolution, &resolution));
|
||||
return EINVAL;
|
||||
m_y_offset = m_framebuffer_height;
|
||||
} else {
|
||||
if (!adapter->set_y_offset(0, 0)) {
|
||||
// FIXME: Find a better KResult here.
|
||||
return KResult(ENOTSUP);
|
||||
}
|
||||
m_framebuffer_width = resolution.width;
|
||||
m_framebuffer_height = resolution.height;
|
||||
m_framebuffer_pitch = m_framebuffer_width * sizeof(u32);
|
||||
|
||||
dbgln_if(FRAMEBUFFER_DEVICE_DEBUG, "New resolution: [{}x{}]", m_framebuffer_width, m_framebuffer_height);
|
||||
resolution.pitch = m_framebuffer_pitch;
|
||||
resolution.width = m_framebuffer_width;
|
||||
resolution.height = m_framebuffer_height;
|
||||
return copy_to_user(user_resolution, &resolution);
|
||||
m_y_offset = 0;
|
||||
}
|
||||
case FB_IOCTL_GET_BUFFER_OFFSET: {
|
||||
auto user_buffer_offset = static_ptr_cast<FBBufferOffset*>(arg);
|
||||
FBBufferOffset buffer_offset;
|
||||
TRY(copy_from_user(&buffer_offset, user_buffer_offset));
|
||||
if (buffer_offset.buffer_index != 0 && buffer_offset.buffer_index != 1)
|
||||
return EINVAL;
|
||||
buffer_offset.offset = (size_t)buffer_offset.buffer_index * m_framebuffer_pitch * m_framebuffer_height;
|
||||
return copy_to_user(user_buffer_offset, &buffer_offset);
|
||||
}
|
||||
case FB_IOCTL_FLUSH_BUFFERS:
|
||||
return ENOTSUP;
|
||||
default:
|
||||
return EINVAL;
|
||||
};
|
||||
return KSuccess;
|
||||
}
|
||||
KResult FramebufferDevice::flush_head_buffer(size_t)
|
||||
{
|
||||
// Note: This FramebufferDevice class doesn't support flushing.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally reach this code, assert.
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
KResult FramebufferDevice::flush_rectangle(size_t, FBRect const&)
|
||||
{
|
||||
// Note: This FramebufferDevice class doesn't support partial flushing.
|
||||
// We take care to verify this at the GenericFramebufferDevice::ioctl method
|
||||
// so if we happen to accidentally reach this code, assert.
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue