diff --git a/Kernel/API/FB.h b/Kernel/API/FB.h index 7c68f04634..a4a13fa4a8 100644 --- a/Kernel/API/FB.h +++ b/Kernel/API/FB.h @@ -13,39 +13,42 @@ __BEGIN_DECLS -ALWAYS_INLINE int fb_get_size_in_bytes(int fd, size_t* out) +ALWAYS_INLINE int fb_get_properties(int fd, FBProperties* info) { - return ioctl(fd, FB_IOCTL_GET_SIZE_IN_BYTES, out); + return ioctl(fd, FB_IOCTL_GET_PROPERTIES, info); } -ALWAYS_INLINE int fb_get_resolution(int fd, FBResolution* info) +ALWAYS_INLINE int fb_get_head_properties(int fd, FBHeadProperties* info) { - return ioctl(fd, FB_IOCTL_GET_RESOLUTION, info); + return ioctl(fd, FB_IOCTL_GET_HEAD_PROPERTIES, info); } -ALWAYS_INLINE int fb_set_resolution(int fd, FBResolution* info) +ALWAYS_INLINE int fb_get_resolution(int fd, FBHeadResolution* info) { - return ioctl(fd, FB_IOCTL_SET_RESOLUTION, info); + FBHeadProperties head_properties; + head_properties.head_index = info->head_index; + if (auto rc = ioctl(fd, FB_IOCTL_GET_HEAD_PROPERTIES, &head_properties); rc < 0) + return rc; + info->head_index = head_properties.head_index; + info->pitch = head_properties.pitch; + info->width = head_properties.width; + info->height = head_properties.height; + return 0; } -ALWAYS_INLINE int fb_get_buffer_offset(int fd, int index, unsigned* offset) +ALWAYS_INLINE int fb_set_resolution(int fd, FBHeadResolution* info) { - FBBufferOffset fb_buffer_offset; - fb_buffer_offset.buffer_index = index; - int result = ioctl(fd, FB_IOCTL_GET_BUFFER_OFFSET, &fb_buffer_offset); - if (result == 0) - *offset = fb_buffer_offset.offset; - return result; + return ioctl(fd, FB_IOCTL_SET_HEAD_RESOLUTION, info); } -ALWAYS_INLINE int fb_get_buffer(int fd, int* index) +ALWAYS_INLINE int fb_get_head_vertical_offset_buffer(int fd, FBHeadVerticalOffset* vertical_offset) { - return ioctl(fd, FB_IOCTL_GET_BUFFER, index); + return ioctl(fd, FB_IOCTL_GET_HEAD_VERITCAL_OFFSET_BUFFER, vertical_offset); } -ALWAYS_INLINE int fb_set_buffer(int fd, int index) +ALWAYS_INLINE int fb_set_head_vertical_offset_buffer(int fd, FBHeadVerticalOffset* vertical_offset) { - return ioctl(fd, FB_IOCTL_SET_BUFFER, index); + return ioctl(fd, FB_IOCTL_SET_HEAD_VERITCAL_OFFSET_BUFFER, vertical_offset); } ALWAYS_INLINE int fb_flush_buffers(int fd, int index, FBRect const* rects, unsigned count) @@ -54,7 +57,7 @@ ALWAYS_INLINE int fb_flush_buffers(int fd, int index, FBRect const* rects, unsig fb_flush_rects.buffer_index = index; fb_flush_rects.count = count; fb_flush_rects.rects = rects; - return ioctl(fd, FB_IOCTL_FLUSH_BUFFERS, &fb_flush_rects); + return ioctl(fd, FB_IOCTL_FLUSH_HEAD_BUFFERS, &fb_flush_rects); } __END_DECLS diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index f3acac594b..385f6a5d07 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -80,6 +80,7 @@ set(KERNEL_SOURCES Graphics/VirtIOGPU/Console.cpp Graphics/VirtIOGPU/GraphicsAdapter.cpp Graphics/VGACompatibleAdapter.cpp + Graphics/GenericFramebufferDevice.cpp SanCov.cpp Storage/Partition/DiskPartition.cpp Storage/Partition/DiskPartitionMetadata.cpp diff --git a/Kernel/Graphics/Bochs/GraphicsAdapter.cpp b/Kernel/Graphics/Bochs/GraphicsAdapter.cpp index 479be5ba1d..5bca91d48f 100644 --- a/Kernel/Graphics/Bochs/GraphicsAdapter.cpp +++ b/Kernel/Graphics/Bochs/GraphicsAdapter.cpp @@ -127,9 +127,9 @@ UNMAP_AFTER_INIT BochsGraphicsAdapter::BochsGraphicsAdapter(PCI::DeviceIdentifie UNMAP_AFTER_INIT void BochsGraphicsAdapter::initialize_framebuffer_devices() { // FIXME: Find a better way to determine default resolution... - m_framebuffer_device = FramebufferDevice::create(*this, 0, PhysicalAddress(PCI::get_BAR0(pci_address()) & 0xfffffff0), 1024, 768, 1024 * sizeof(u32)); + m_framebuffer_device = FramebufferDevice::create(*this, PhysicalAddress(PCI::get_BAR0(pci_address()) & 0xfffffff0), 1024, 768, 1024 * sizeof(u32)); // FIXME: Would be nice to be able to return a KResult here. - VERIFY(!m_framebuffer_device->initialize().is_error()); + VERIFY(!m_framebuffer_device->try_to_initialize().is_error()); } bool BochsGraphicsAdapter::vga_compatible() const diff --git a/Kernel/Graphics/FramebufferDevice.cpp b/Kernel/Graphics/FramebufferDevice.cpp index 56db1bc65f..fae6bf5f0d 100644 --- a/Kernel/Graphics/FramebufferDevice.cpp +++ b/Kernel/Graphics/FramebufferDevice.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -16,14 +17,11 @@ #include #include -#define MAX_RESOLUTION_WIDTH 4096 -#define MAX_RESOLUTION_HEIGHT 2160 - namespace Kernel { -NonnullRefPtr FramebufferDevice::create(const GenericGraphicsAdapter& adapter, size_t output_port_index, PhysicalAddress paddr, size_t width, size_t height, size_t pitch) +NonnullRefPtr 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(adapter, output_port_index, paddr, width, height, pitch); + auto framebuffer_device_or_error = DeviceManagement::try_create_device(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 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 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 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 arg) +KResultOr FramebufferDevice::pitch(size_t head) const { - REQUIRE_PROMISE(video); - switch (request) { - case FB_IOCTL_GET_SIZE_IN_BYTES: { - auto user_size = static_ptr_cast(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(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(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(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(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 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 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 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 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(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(); } } diff --git a/Kernel/Graphics/FramebufferDevice.h b/Kernel/Graphics/FramebufferDevice.h index ad29892e81..4ad5288367 100644 --- a/Kernel/Graphics/FramebufferDevice.h +++ b/Kernel/Graphics/FramebufferDevice.h @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include @@ -17,38 +17,38 @@ namespace Kernel { -class FramebufferDevice : public BlockDevice { +class FramebufferDevice final : public GenericFramebufferDevice { AK_MAKE_ETERNAL friend class DeviceManagement; public: - static NonnullRefPtr create(const GenericGraphicsAdapter&, size_t, PhysicalAddress, size_t, size_t, size_t); + static NonnullRefPtr create(const GenericGraphicsAdapter&, PhysicalAddress, size_t, size_t, size_t); - virtual KResult ioctl(OpenFileDescription&, unsigned request, Userspace arg) override; virtual KResultOr mmap(Process&, OpenFileDescription&, Memory::VirtualRange const&, u64 offset, int prot, bool shared) override; - virtual void deactivate_writes(); - virtual void activate_writes(); - size_t framebuffer_size_in_bytes() const; + virtual void deactivate_writes() override; + virtual void activate_writes() override; - virtual ~FramebufferDevice() {}; - KResult initialize(); + virtual KResult try_to_initialize() override; -protected: - FramebufferDevice(const GenericGraphicsAdapter&, size_t); - NonnullRefPtr m_graphics_adapter; + virtual bool multihead_support() const override { return false; } + virtual bool flushing_support() const override { return false; } + virtual bool partial_flushing_support() const override { return false; } + virtual size_t heads_count() const override { return 1; } + virtual KResultOr buffer_length(size_t head) const override; + virtual KResultOr pitch(size_t head) const override; + virtual KResultOr height(size_t head) const override; + virtual KResultOr width(size_t head) const override; + virtual KResultOr vertical_offset(size_t head) const override; + virtual KResultOr vertical_offseted(size_t head) const override; private: - FramebufferDevice(const GenericGraphicsAdapter&, size_t, PhysicalAddress, size_t, size_t, size_t); + virtual KResult set_head_resolution(size_t head, size_t width, size_t height, size_t pitch) override; + virtual KResult set_head_buffer(size_t head, bool second_buffer) override; + virtual KResult flush_head_buffer(size_t head) override; + virtual KResult flush_rectangle(size_t head, FBRect const&) override; - // ^File - virtual StringView class_name() const override { return "FramebufferDevice"sv; } - - virtual bool can_read(const OpenFileDescription&, size_t) const override final { return true; } - virtual bool can_write(const OpenFileDescription&, size_t) const override final { return true; } - virtual void start_request(AsyncBlockDeviceRequest& request) override final { request.complete(AsyncDeviceRequest::Failure); } - virtual KResultOr read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override { return EINVAL; } - virtual KResultOr write(OpenFileDescription&, u64, const UserOrKernelBuffer&, size_t) override { return EINVAL; } + FramebufferDevice(const GenericGraphicsAdapter&, PhysicalAddress, size_t, size_t, size_t); PhysicalAddress m_framebuffer_address; size_t m_framebuffer_pitch { 0 }; @@ -56,6 +56,7 @@ private: size_t m_framebuffer_height { 0 }; Spinlock m_activation_lock; + mutable Mutex m_buffer_offset_lock; RefPtr m_real_framebuffer_vmobject; RefPtr m_swapped_framebuffer_vmobject; @@ -68,7 +69,6 @@ private: Memory::Region* m_userspace_framebuffer_region { nullptr }; size_t m_y_offset { 0 }; - size_t m_output_port_index; }; } diff --git a/Kernel/Graphics/GenericFramebufferDevice.cpp b/Kernel/Graphics/GenericFramebufferDevice.cpp new file mode 100644 index 0000000000..3ac1a0a0ee --- /dev/null +++ b/Kernel/Graphics/GenericFramebufferDevice.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2021, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_RESOLUTION_WIDTH 4096 +#define MAX_RESOLUTION_HEIGHT 2160 + +namespace Kernel { + +KResult GenericFramebufferDevice::verify_head_index(int head_index) const +{ + if (head_index < 0) + return KResult(EINVAL); + if (!multihead_support() && head_index > 0) + return KResult(ENOTSUP); + return KSuccess; +} + +KResult GenericFramebufferDevice::ioctl(OpenFileDescription&, unsigned request, Userspace arg) +{ + REQUIRE_PROMISE(video); + switch (request) { + case FB_IOCTL_GET_PROPERTIES: { + auto user_properties = static_ptr_cast(arg); + FBProperties properties; + auto adapter = m_graphics_adapter.strong_ref(); + if (!adapter) + return KResult(EIO); + properties.multihead_support = multihead_support(); + properties.flushing_support = flushing_support(); + properties.doublebuffer_support = adapter->double_framebuffering_capable(); + properties.partial_flushing_support = partial_flushing_support(); + return copy_to_user(user_properties, &properties); + } + case FB_IOCTL_GET_HEAD_PROPERTIES: { + auto user_head_properties = static_ptr_cast(arg); + FBHeadProperties head_properties; + TRY(copy_from_user(&head_properties, user_head_properties)); + TRY(verify_head_index(head_properties.head_index)); + + head_properties.pitch = TRY(pitch(head_properties.head_index)); + head_properties.width = TRY(width(head_properties.head_index)); + head_properties.height = TRY(height(head_properties.head_index)); + head_properties.buffer_length = TRY(buffer_length(head_properties.head_index)); + head_properties.offset = TRY(vertical_offset(head_properties.head_index)); + return copy_to_user(user_head_properties, &head_properties); + } + case FB_IOCTL_SET_HEAD_RESOLUTION: { + auto user_head_resolution = static_ptr_cast(arg); + FBHeadResolution head_resolution; + TRY(copy_from_user(&head_resolution, user_head_resolution)); + TRY(verify_head_index(head_resolution.head_index)); + + if (head_resolution.pitch < 0) + return KResult(EINVAL); + if (head_resolution.width < 0) + return KResult(EINVAL); + if (head_resolution.height < 0) + return KResult(EINVAL); + TRY(set_head_resolution(head_resolution.head_index, head_resolution.width, head_resolution.height, head_resolution.pitch)); + return KSuccess; + } + case FB_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER: { + auto user_head_vertical_buffer_offset = static_ptr_cast(arg); + FBHeadVerticalOffset head_vertical_buffer_offset; + TRY(copy_from_user(&head_vertical_buffer_offset, user_head_vertical_buffer_offset)); + TRY(verify_head_index(head_vertical_buffer_offset.head_index)); + + if (head_vertical_buffer_offset.offseted < 0 || head_vertical_buffer_offset.offseted > 1) + return KResult(EINVAL); + TRY(set_head_buffer(head_vertical_buffer_offset.head_index, head_vertical_buffer_offset.offseted)); + return KSuccess; + } + case FB_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER: { + auto user_head_vertical_buffer_offset = static_ptr_cast(arg); + FBHeadVerticalOffset head_vertical_buffer_offset; + TRY(copy_from_user(&head_vertical_buffer_offset, user_head_vertical_buffer_offset)); + TRY(verify_head_index(head_vertical_buffer_offset.head_index)); + + head_vertical_buffer_offset.offseted = TRY(vertical_offseted(head_vertical_buffer_offset.head_index)); + return copy_to_user(user_head_vertical_buffer_offset, &head_vertical_buffer_offset); + } + case FB_IOCTL_FLUSH_HEAD_BUFFERS: { + if (!partial_flushing_support()) + return KResult(ENOTSUP); + auto user_flush_rects = static_ptr_cast(arg); + FBFlushRects flush_rects; + TRY(copy_from_user(&flush_rects, user_flush_rects)); + if (Checked::multiplication_would_overflow(flush_rects.count, sizeof(FBRect))) + return KResult(EFAULT); + MutexLocker locker(m_flushing_lock); + if (flush_rects.count > 0) { + for (unsigned i = 0; i < flush_rects.count; i++) { + FBRect user_dirty_rect; + TRY(copy_from_user(&user_dirty_rect, &flush_rects.rects[i])); + TRY(flush_rectangle(flush_rects.buffer_index, user_dirty_rect)); + } + } + return KSuccess; + }; + case FB_IOCTL_FLUSH_HEAD: { + if (!flushing_support()) + return KResult(ENOTSUP); + // Note: We accept a FBRect, but we only really care about the head_index value. + auto user_rect = static_ptr_cast(arg); + FBRect rect; + TRY(copy_from_user(&rect, user_rect)); + TRY(verify_head_index(rect.head_index)); + + TRY(flush_head_buffer(rect.head_index)); + return KSuccess; + } + default: + return EINVAL; + }; +} + +GenericFramebufferDevice::GenericFramebufferDevice(const GenericGraphicsAdapter& adapter) + : BlockDevice(29, GraphicsManagement::the().allocate_minor_device_number()) + , m_graphics_adapter(adapter) +{ +} + +} diff --git a/Kernel/Graphics/GenericFramebufferDevice.h b/Kernel/Graphics/GenericFramebufferDevice.h new file mode 100644 index 0000000000..6fbce789dd --- /dev/null +++ b/Kernel/Graphics/GenericFramebufferDevice.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Kernel { + +class GenericFramebufferDevice : public BlockDevice { + AK_MAKE_ETERNAL + friend class DeviceManagement; + +public: + virtual KResult try_to_initialize() = 0; + + virtual void deactivate_writes() = 0; + virtual void activate_writes() = 0; + + virtual ~GenericFramebufferDevice() = default; + + // ^File + virtual KResultOr mmap(Process&, OpenFileDescription&, Memory::VirtualRange const&, u64 offset, int prot, bool shared) = 0; + virtual KResult ioctl(OpenFileDescription&, unsigned request, Userspace arg) override final; + virtual StringView class_name() const override final { return "FramebufferDevice"sv; } + +private: + // ^File + virtual bool can_read(const OpenFileDescription&, size_t) const override final { return true; } + virtual bool can_write(const OpenFileDescription&, size_t) const override final { return true; } + virtual void start_request(AsyncBlockDeviceRequest& request) override final { request.complete(AsyncDeviceRequest::Failure); } + virtual KResultOr read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override { return EINVAL; } + virtual KResultOr write(OpenFileDescription&, u64, const UserOrKernelBuffer&, size_t) override { return EINVAL; } + +protected: + virtual bool multihead_support() const = 0; + virtual bool flushing_support() const = 0; + virtual bool partial_flushing_support() const = 0; + virtual size_t heads_count() const = 0; + virtual KResultOr buffer_length(size_t head) const = 0; + virtual KResultOr pitch(size_t head) const = 0; + virtual KResultOr height(size_t head) const = 0; + virtual KResultOr width(size_t head) const = 0; + virtual KResultOr vertical_offset(size_t head) const = 0; + virtual KResultOr vertical_offseted(size_t head) const = 0; + + virtual KResult set_head_resolution(size_t head, size_t width, size_t height, size_t pitch) = 0; + virtual KResult set_head_buffer(size_t head, bool second_buffer) = 0; + virtual KResult flush_head_buffer(size_t head) = 0; + // FIXME: This method is too much specific to the VirtIO implementation (especially the buffer_index parameter) + virtual KResult flush_rectangle(size_t buffer_index, FBRect const&) = 0; + + KResult verify_head_index(int head_index) const; + + GenericFramebufferDevice(const GenericGraphicsAdapter&); + mutable WeakPtr m_graphics_adapter; + mutable Mutex m_heads_lock; + mutable Mutex m_flushing_lock; + mutable Mutex m_resolution_lock; +}; + +} diff --git a/Kernel/Graphics/GenericGraphicsAdapter.h b/Kernel/Graphics/GenericGraphicsAdapter.h index fe46ab2084..1113ba0964 100644 --- a/Kernel/Graphics/GenericGraphicsAdapter.h +++ b/Kernel/Graphics/GenericGraphicsAdapter.h @@ -8,12 +8,15 @@ #include #include +#include #include #include #include namespace Kernel { -class GenericGraphicsAdapter : public RefCounted { +class GenericGraphicsAdapter + : public RefCounted + , public Weakable { public: virtual ~GenericGraphicsAdapter() = default; virtual void initialize_framebuffer_devices() = 0; diff --git a/Kernel/Graphics/Intel/NativeGraphicsAdapter.cpp b/Kernel/Graphics/Intel/NativeGraphicsAdapter.cpp index b8f40468ac..71d79dcf0f 100644 --- a/Kernel/Graphics/Intel/NativeGraphicsAdapter.cpp +++ b/Kernel/Graphics/Intel/NativeGraphicsAdapter.cpp @@ -642,8 +642,9 @@ void IntelNativeGraphicsAdapter::initialize_framebuffer_devices() VERIFY(m_framebuffer_pitch != 0); VERIFY(m_framebuffer_height != 0); VERIFY(m_framebuffer_width != 0); - m_framebuffer_device = FramebufferDevice::create(*this, 0, address, m_framebuffer_width, m_framebuffer_height, m_framebuffer_pitch); + m_framebuffer_device = FramebufferDevice::create(*this, address, m_framebuffer_width, m_framebuffer_height, m_framebuffer_pitch); // FIXME: Would be nice to be able to return a KResult here. - VERIFY(!m_framebuffer_device->initialize().is_error()); + auto framebuffer_result = m_framebuffer_device->try_to_initialize(); + VERIFY(!framebuffer_result.is_error()); } } diff --git a/Kernel/Graphics/VGACompatibleAdapter.cpp b/Kernel/Graphics/VGACompatibleAdapter.cpp index c8c17020e9..4950051915 100644 --- a/Kernel/Graphics/VGACompatibleAdapter.cpp +++ b/Kernel/Graphics/VGACompatibleAdapter.cpp @@ -31,9 +31,9 @@ UNMAP_AFTER_INIT void VGACompatibleAdapter::initialize_framebuffer_devices() VERIFY(m_framebuffer_width != 0); VERIFY(m_framebuffer_height != 0); VERIFY(m_framebuffer_pitch != 0); - m_framebuffer_device = FramebufferDevice::create(*this, 0, m_framebuffer_address, m_framebuffer_width, m_framebuffer_height, m_framebuffer_pitch); + m_framebuffer_device = FramebufferDevice::create(*this, m_framebuffer_address, m_framebuffer_width, m_framebuffer_height, m_framebuffer_pitch); // FIXME: Would be nice to be able to return KResult here. - VERIFY(!m_framebuffer_device->initialize().is_error()); + VERIFY(!m_framebuffer_device->try_to_initialize().is_error()); } UNMAP_AFTER_INIT VGACompatibleAdapter::VGACompatibleAdapter(PCI::Address address) diff --git a/Kernel/Graphics/VirtIOGPU/Console.cpp b/Kernel/Graphics/VirtIOGPU/Console.cpp index 2ecefebf67..38d7a63a83 100644 --- a/Kernel/Graphics/VirtIOGPU/Console.cpp +++ b/Kernel/Graphics/VirtIOGPU/Console.cpp @@ -42,9 +42,9 @@ Console::Console(RefPtr const& framebuffer_device) enqueue_refresh_timer(); } -void Console::set_resolution(size_t width, size_t height, size_t) +void Console::set_resolution(size_t width, size_t height, size_t pitch) { - auto did_set_resolution = m_framebuffer_device->try_to_set_resolution(width, height); + auto did_set_resolution = m_framebuffer_device->set_head_resolution(0, width, height, pitch); VERIFY(!did_set_resolution.is_error()); } diff --git a/Kernel/Graphics/VirtIOGPU/FramebufferDevice.cpp b/Kernel/Graphics/VirtIOGPU/FramebufferDevice.cpp index b8fe17ca5a..0c20aa655f 100644 --- a/Kernel/Graphics/VirtIOGPU/FramebufferDevice.cpp +++ b/Kernel/Graphics/VirtIOGPU/FramebufferDevice.cpp @@ -11,18 +11,137 @@ namespace Kernel::Graphics::VirtIOGPU { -GraphicsAdapter const& FramebufferDevice::adapter() const +RefPtr FramebufferDevice::adapter() const { - return static_cast(*m_graphics_adapter); + auto adapter = m_graphics_adapter.strong_ref(); + // FIXME: Propagate error gracefully + VERIFY(adapter); + return static_cast(*adapter); } -GraphicsAdapter& FramebufferDevice::adapter() +KResultOr FramebufferDevice::buffer_length(size_t head) const { - return static_cast(*m_graphics_adapter); + // 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 display_info().rect.width * display_info().rect.height * 4; +} +KResultOr FramebufferDevice::pitch(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 display_info().rect.width * 4; +} +KResultOr 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 display_info().rect.height; +} +KResultOr 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 display_info().rect.width; +} +KResultOr 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); + return 0; +} +KResultOr 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); + return false; +} + +KResult FramebufferDevice::set_head_resolution(size_t head, size_t width, size_t height, size_t) +{ + // Note: This class doesn't support multihead setup (yet!). + // 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); + if (width > MAX_VIRTIOGPU_RESOLUTION_WIDTH || height > MAX_VIRTIOGPU_RESOLUTION_HEIGHT) + return KResult(ENOTSUP); + + auto& info = display_info(); + + MutexLocker locker(adapter()->operation_lock()); + + info.rect = { + .x = 0, + .y = 0, + .width = (u32)width, + .height = (u32)height, + }; + + // FIXME: Would be nice to be able to return KResultOr here. + TRY(create_framebuffer()); + return KSuccess; +} +KResult FramebufferDevice::set_head_buffer(size_t, bool) +{ + return KResult(ENOTSUP); +} +KResult FramebufferDevice::flush_head_buffer(size_t) +{ + // Note: This 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 buffer_index, FBRect const& rect) +{ + MutexLocker locker(adapter()->operation_lock()); + Protocol::Rect dirty_rect { + .x = rect.x, + .y = rect.y, + .width = rect.width, + .height = rect.height + }; + // FIXME: Find a better KResult here. + if (!m_are_writes_active) + return KResult(EIO); + auto& buffer = buffer_from_index(buffer_index); + transfer_framebuffer_data_to_host(dirty_rect, buffer); + if (&buffer == m_current_buffer) { + // Flushing directly to screen + flush_displayed_image(dirty_rect, buffer); + buffer.dirty_rect = {}; + } else { + if (buffer.dirty_rect.width == 0 || buffer.dirty_rect.height == 0) { + buffer.dirty_rect = dirty_rect; + } else { + auto current_dirty_right = buffer.dirty_rect.x + buffer.dirty_rect.width; + auto current_dirty_bottom = buffer.dirty_rect.y + buffer.dirty_rect.height; + buffer.dirty_rect.x = min(buffer.dirty_rect.x, dirty_rect.x); + buffer.dirty_rect.y = min(buffer.dirty_rect.y, dirty_rect.y); + buffer.dirty_rect.width = max(current_dirty_right, dirty_rect.x + dirty_rect.width) - buffer.dirty_rect.x; + buffer.dirty_rect.height = max(current_dirty_bottom, dirty_rect.y + dirty_rect.height) - buffer.dirty_rect.y; + } + } + return KSuccess; } FramebufferDevice::FramebufferDevice(GraphicsAdapter const& adapter, ScanoutID scanout) - : Kernel::FramebufferDevice(adapter, scanout.value()) + : GenericFramebufferDevice(adapter) , m_scanout(scanout) { if (display_info().enabled) { @@ -55,7 +174,7 @@ KResult FramebufferDevice::create_framebuffer() } m_framebuffer_sink_vmobject = TRY(Memory::AnonymousVMObject::try_create_with_physical_pages(pages.span())); - MutexLocker locker(adapter().operation_lock()); + MutexLocker locker(adapter()->operation_lock()); m_current_buffer = &buffer_from_index(m_last_set_buffer_index.load()); create_buffer(m_main_buffer, 0, m_buffer_size); create_buffer(m_back_buffer, m_buffer_size, m_buffer_size); @@ -72,14 +191,14 @@ void FramebufferDevice::create_buffer(Buffer& buffer, size_t framebuffer_offset, // 1. Create BUFFER using VIRTIO_GPU_CMD_RESOURCE_CREATE_2D if (buffer.resource_id.value() != 0) - adapter().delete_resource(buffer.resource_id); - buffer.resource_id = adapter().create_2d_resource(info.rect); + adapter()->delete_resource(buffer.resource_id); + buffer.resource_id = adapter()->create_2d_resource(info.rect); // 2. Attach backing storage using VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING - adapter().ensure_backing_storage(buffer.resource_id, *m_framebuffer, buffer.framebuffer_offset, framebuffer_size); + adapter()->ensure_backing_storage(buffer.resource_id, *m_framebuffer, buffer.framebuffer_offset, framebuffer_size); // 3. Use VIRTIO_GPU_CMD_SET_SCANOUT to link the framebuffer to a display scanout. if (&buffer == m_current_buffer) - adapter().set_scanout_resource(m_scanout.value(), buffer.resource_id, info.rect); + adapter()->set_scanout_resource(m_scanout.value(), buffer.resource_id, info.rect); // 4. Render our test pattern draw_ntsc_test_pattern(buffer); // 5. Use VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D to update the host resource from guest memory. @@ -101,148 +220,41 @@ void FramebufferDevice::create_buffer(Buffer& buffer, size_t framebuffer_offset, Protocol::DisplayInfoResponse::Display const& FramebufferDevice::display_info() const { - return adapter().display_info(m_scanout); + return adapter()->display_info(m_scanout); } Protocol::DisplayInfoResponse::Display& FramebufferDevice::display_info() { - return adapter().display_info(m_scanout); + return adapter()->display_info(m_scanout); } void FramebufferDevice::transfer_framebuffer_data_to_host(Protocol::Rect const& rect, Buffer& buffer) { - adapter().transfer_framebuffer_data_to_host(m_scanout, buffer.resource_id, rect); + adapter()->transfer_framebuffer_data_to_host(m_scanout, buffer.resource_id, rect); } void FramebufferDevice::flush_dirty_window(Protocol::Rect const& dirty_rect, Buffer& buffer) { - adapter().flush_dirty_rectangle(m_scanout, buffer.resource_id, dirty_rect); + adapter()->flush_dirty_rectangle(m_scanout, buffer.resource_id, dirty_rect); } void FramebufferDevice::flush_displayed_image(Protocol::Rect const& dirty_rect, Buffer& buffer) { - adapter().flush_displayed_image(buffer.resource_id, dirty_rect); -} - -KResult FramebufferDevice::try_to_set_resolution(size_t width, size_t height) -{ - if (width > MAX_VIRTIOGPU_RESOLUTION_WIDTH || height > MAX_VIRTIOGPU_RESOLUTION_HEIGHT) - return EINVAL; - - auto& info = display_info(); - - MutexLocker locker(adapter().operation_lock()); - - info.rect = { - .x = 0, - .y = 0, - .width = (u32)width, - .height = (u32)height, - }; - - return create_framebuffer(); + adapter()->flush_displayed_image(buffer.resource_id, dirty_rect); } void FramebufferDevice::set_buffer(int buffer_index) { auto& buffer = buffer_index == 0 ? m_main_buffer : m_back_buffer; - MutexLocker locker(adapter().operation_lock()); + MutexLocker locker(adapter()->operation_lock()); if (&buffer == m_current_buffer) return; m_current_buffer = &buffer; - adapter().set_scanout_resource(m_scanout.value(), buffer.resource_id, display_info().rect); - adapter().flush_displayed_image(buffer.resource_id, buffer.dirty_rect); // QEMU SDL backend requires this (as per spec) + adapter()->set_scanout_resource(m_scanout.value(), buffer.resource_id, display_info().rect); + adapter()->flush_displayed_image(buffer.resource_id, buffer.dirty_rect); // QEMU SDL backend requires this (as per spec) buffer.dirty_rect = {}; } -KResult FramebufferDevice::ioctl(OpenFileDescription&, unsigned request, Userspace arg) -{ - REQUIRE_PROMISE(video); - switch (request) { - case FB_IOCTL_GET_SIZE_IN_BYTES: { - auto out = static_ptr_cast(arg); - size_t value = m_buffer_size * 2; - return copy_to_user(out, &value); - } - case FB_IOCTL_SET_RESOLUTION: { - auto user_resolution = static_ptr_cast(arg); - FBResolution resolution; - TRY(copy_from_user(&resolution, user_resolution)); - TRY(try_to_set_resolution(resolution.width, resolution.height)); - resolution.pitch = pitch(); - return copy_to_user(user_resolution, &resolution); - } - case FB_IOCTL_GET_RESOLUTION: { - auto user_resolution = static_ptr_cast(arg); - FBResolution resolution {}; - resolution.pitch = pitch(); - resolution.width = width(); - resolution.height = height(); - return copy_to_user(user_resolution, &resolution); - } - case FB_IOCTL_SET_BUFFER: { - auto buffer_index = static_cast(arg.ptr()); - if (!is_valid_buffer_index(buffer_index)) - return EINVAL; - if (m_last_set_buffer_index.exchange(buffer_index) != buffer_index && m_are_writes_active) - set_buffer(buffer_index); - return KSuccess; - } - case FB_IOCTL_FLUSH_BUFFERS: { - auto user_flush_rects = static_ptr_cast(arg); - FBFlushRects flush_rects; - TRY(copy_from_user(&flush_rects, user_flush_rects)); - if (!is_valid_buffer_index(flush_rects.buffer_index)) - return EINVAL; - if (Checked::multiplication_would_overflow(flush_rects.count, sizeof(FBRect))) - return EFAULT; - if (m_are_writes_active && flush_rects.count > 0) { - auto& buffer = buffer_from_index(flush_rects.buffer_index); - MutexLocker locker(adapter().operation_lock()); - for (unsigned i = 0; i < flush_rects.count; i++) { - FBRect user_dirty_rect; - TRY(copy_from_user(&user_dirty_rect, &flush_rects.rects[i])); - Protocol::Rect dirty_rect { - .x = user_dirty_rect.x, - .y = user_dirty_rect.y, - .width = user_dirty_rect.width, - .height = user_dirty_rect.height - }; - transfer_framebuffer_data_to_host(dirty_rect, buffer); - if (&buffer == m_current_buffer) { - // Flushing directly to screen - flush_displayed_image(dirty_rect, buffer); - buffer.dirty_rect = {}; - } else { - if (buffer.dirty_rect.width == 0 || buffer.dirty_rect.height == 0) { - buffer.dirty_rect = dirty_rect; - } else { - auto current_dirty_right = buffer.dirty_rect.x + buffer.dirty_rect.width; - auto current_dirty_bottom = buffer.dirty_rect.y + buffer.dirty_rect.height; - buffer.dirty_rect.x = min(buffer.dirty_rect.x, dirty_rect.x); - buffer.dirty_rect.y = min(buffer.dirty_rect.y, dirty_rect.y); - buffer.dirty_rect.width = max(current_dirty_right, dirty_rect.x + dirty_rect.width) - buffer.dirty_rect.x; - buffer.dirty_rect.height = max(current_dirty_bottom, dirty_rect.y + dirty_rect.height) - buffer.dirty_rect.y; - } - } - } - } - return KSuccess; - } - case FB_IOCTL_GET_BUFFER_OFFSET: { - auto user_buffer_offset = static_ptr_cast(arg); - FBBufferOffset buffer_offset; - TRY(copy_from_user(&buffer_offset, user_buffer_offset)); - if (!is_valid_buffer_index(buffer_offset.buffer_index)) - return EINVAL; - buffer_offset.offset = (size_t)buffer_offset.buffer_index * m_buffer_size; - return copy_to_user(user_buffer_offset, &buffer_offset); - } - default: - return EINVAL; - }; -} - KResultOr FramebufferDevice::mmap(Process& process, OpenFileDescription&, Memory::VirtualRange const& range, u64 offset, int prot, bool shared) { REQUIRE_PROMISE(video); diff --git a/Kernel/Graphics/VirtIOGPU/FramebufferDevice.h b/Kernel/Graphics/VirtIOGPU/FramebufferDevice.h index 42bb524a92..b643691fb2 100644 --- a/Kernel/Graphics/VirtIOGPU/FramebufferDevice.h +++ b/Kernel/Graphics/VirtIOGPU/FramebufferDevice.h @@ -15,7 +15,7 @@ namespace Kernel::Graphics::VirtIOGPU { class GraphicsAdapter; -class FramebufferDevice final : public Kernel::FramebufferDevice { +class FramebufferDevice final : public GenericFramebufferDevice { friend class Console; struct Buffer { size_t framebuffer_offset { 0 }; @@ -28,12 +28,11 @@ public: FramebufferDevice(GraphicsAdapter const&, ScanoutID); virtual ~FramebufferDevice() override; + virtual KResult try_to_initialize() override { return KSuccess; } + virtual void deactivate_writes(); virtual void activate_writes(); - KResult try_to_set_resolution(size_t width, size_t height); - void clear_to_black(Buffer&); - size_t width() const { return display_info().rect.width; } size_t height() const { return display_info().rect.height; } size_t pitch() const { return display_info().rect.width * 4; } @@ -44,25 +43,40 @@ public: return Memory::page_round_up(sizeof(u32) * width * height); } + u8* framebuffer_data(); + +private: + virtual bool multihead_support() const override { return false; } + virtual bool flushing_support() const override { return false; } + virtual bool partial_flushing_support() const override { return true; } + virtual size_t heads_count() const override { return 1; } + virtual KResultOr buffer_length(size_t head) const override; + virtual KResultOr pitch(size_t head) const override; + virtual KResultOr height(size_t head) const override; + virtual KResultOr width(size_t head) const override; + virtual KResultOr vertical_offset(size_t head) const override; + virtual KResultOr vertical_offseted(size_t head) const override; + + virtual KResult set_head_resolution(size_t head, size_t width, size_t height, size_t pitch) override; + virtual KResult set_head_buffer(size_t head, bool second_buffer) override; + virtual KResult flush_head_buffer(size_t head) override; + virtual KResult flush_rectangle(size_t head, FBRect const&) override; + void flush_dirty_window(Protocol::Rect const&, Buffer&); void transfer_framebuffer_data_to_host(Protocol::Rect const&, Buffer&); void flush_displayed_image(Protocol::Rect const&, Buffer&); void draw_ntsc_test_pattern(Buffer&); - u8* framebuffer_data(); - -private: - virtual StringView class_name() const override { return "VirtIOFrameBuffer"sv; } - Protocol::DisplayInfoResponse::Display const& display_info() const; Protocol::DisplayInfoResponse::Display& display_info(); + void clear_to_black(Buffer&); + KResult create_framebuffer(); void create_buffer(Buffer&, size_t, size_t); void set_buffer(int); - virtual KResult ioctl(OpenFileDescription&, unsigned request, Userspace arg) override; virtual KResultOr mmap(Process&, OpenFileDescription&, Memory::VirtualRange const&, u64 offset, int prot, bool shared) override; static bool is_valid_buffer_index(int buffer_index) @@ -76,8 +90,7 @@ private: } Buffer& current_buffer() const { return *m_current_buffer; } - GraphicsAdapter const& adapter() const; - GraphicsAdapter& adapter(); + RefPtr adapter() const; const ScanoutID m_scanout; Buffer* m_current_buffer { nullptr }; diff --git a/Tests/Kernel/bxvga-mmap-kernel-into-userspace.cpp b/Tests/Kernel/bxvga-mmap-kernel-into-userspace.cpp index 43df0bb34a..3dc0f566d2 100644 --- a/Tests/Kernel/bxvga-mmap-kernel-into-userspace.cpp +++ b/Tests/Kernel/bxvga-mmap-kernel-into-userspace.cpp @@ -25,18 +25,20 @@ int main() size_t pitch = width * 4; size_t framebuffer_size_in_bytes = pitch * height * 2; - FBResolution original_resolution; - if (ioctl(fd, FB_IOCTL_GET_RESOLUTION, &original_resolution) < 0) { + FBHeadProperties original_properties; + original_properties.head_index = 0; + if (ioctl(fd, FB_IOCTL_GET_HEAD_PROPERTIES, &original_properties) < 0) { perror("ioctl"); return 1; } - FBResolution resolution; + FBHeadResolution resolution; + resolution.head_index = 0; resolution.width = width; resolution.height = height; resolution.pitch = pitch; - if (ioctl(fd, FB_IOCTL_SET_RESOLUTION, &resolution) < 0) { + if (ioctl(fd, FB_IOCTL_SET_HEAD_RESOLUTION, &resolution) < 0) { perror("ioctl"); return 1; } @@ -87,7 +89,12 @@ int main() process->uid = 0; } - if (ioctl(fd, FB_IOCTL_SET_RESOLUTION, &original_resolution) < 0) { + FBHeadResolution original_resolution; + original_resolution.head_index = 0; + original_resolution.width = original_properties.width; + original_resolution.height = original_properties.height; + original_resolution.pitch = original_properties.pitch; + if (ioctl(fd, FB_IOCTL_SET_HEAD_RESOLUTION, &original_resolution) < 0) { perror("ioctl"); return 1; } diff --git a/Userland/DevTools/UserspaceEmulator/Emulator_syscalls.cpp b/Userland/DevTools/UserspaceEmulator/Emulator_syscalls.cpp index 4fd60b2f2c..a3f65e10c0 100644 --- a/Userland/DevTools/UserspaceEmulator/Emulator_syscalls.cpp +++ b/Userland/DevTools/UserspaceEmulator/Emulator_syscalls.cpp @@ -1155,20 +1155,20 @@ int Emulator::virt$ioctl([[maybe_unused]] int fd, unsigned request, [[maybe_unus if (request == TIOCNOTTY || request == TIOCSCTTY) { return syscall(SC_ioctl, fd, request, 0); } - if (request == FB_IOCTL_GET_SIZE_IN_BYTES) { + if (request == FB_IOCTL_GET_PROPERTIES) { size_t size = 0; auto rc = syscall(SC_ioctl, fd, request, &size); mmu().copy_to_vm(arg, &size, sizeof(size)); return rc; } - if (request == FB_IOCTL_SET_RESOLUTION) { - FBResolution user_resolution; + if (request == FB_IOCTL_SET_HEAD_RESOLUTION) { + FBHeadResolution user_resolution; mmu().copy_from_vm(&user_resolution, arg, sizeof(user_resolution)); auto rc = syscall(SC_ioctl, fd, request, &user_resolution); mmu().copy_to_vm(arg, &user_resolution, sizeof(user_resolution)); return rc; } - if (request == FB_IOCTL_SET_BUFFER) { + if (request == FB_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER) { return syscall(SC_ioctl, fd, request, arg); } reportln("Unsupported ioctl: {}", request); diff --git a/Userland/Libraries/LibC/sys/ioctl_numbers.h b/Userland/Libraries/LibC/sys/ioctl_numbers.h index 8da1f27da3..ca715672c5 100644 --- a/Userland/Libraries/LibC/sys/ioctl_numbers.h +++ b/Userland/Libraries/LibC/sys/ioctl_numbers.h @@ -18,13 +18,38 @@ struct winsize { unsigned short ws_ypixel; }; -struct FBResolution { +struct FBProperties { + bool multihead_support; + bool doublebuffer_support; + bool flushing_support; + bool partial_flushing_support; +}; + +struct FBHeadProperties { + int head_index; + unsigned pitch; unsigned width; unsigned height; + + unsigned offset; + unsigned buffer_length; +}; + +struct FBHeadResolution { + int head_index; + int pitch; + int width; + int height; +}; + +struct FBHeadVerticalOffset { + int head_index; + int offseted; }; struct FBRect { + int head_index; unsigned x; unsigned y; unsigned width; @@ -57,13 +82,13 @@ enum IOCtlNumber { TIOCSTI, TIOCNOTTY, TIOCSWINSZ, - FB_IOCTL_GET_SIZE_IN_BYTES, - FB_IOCTL_GET_RESOLUTION, - FB_IOCTL_SET_RESOLUTION, - FB_IOCTL_GET_BUFFER, - FB_IOCTL_GET_BUFFER_OFFSET, - FB_IOCTL_SET_BUFFER, - FB_IOCTL_FLUSH_BUFFERS, + FB_IOCTL_GET_PROPERTIES, + FB_IOCTL_GET_HEAD_PROPERTIES, + FB_IOCTL_SET_HEAD_RESOLUTION, + FB_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER, + FB_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER, + FB_IOCTL_FLUSH_HEAD_BUFFERS, + FB_IOCTL_FLUSH_HEAD, KEYBOARD_IOCTL_GET_NUM_LOCK, KEYBOARD_IOCTL_SET_NUM_LOCK, KEYBOARD_IOCTL_GET_CAPS_LOCK, @@ -105,13 +130,13 @@ enum IOCtlNumber { #define TIOCSTI TIOCSTI #define TIOCNOTTY TIOCNOTTY #define TIOCSWINSZ TIOCSWINSZ -#define FB_IOCTL_GET_SIZE_IN_BYTES FB_IOCTL_GET_SIZE_IN_BYTES -#define FB_IOCTL_GET_RESOLUTION FB_IOCTL_GET_RESOLUTION -#define FB_IOCTL_SET_RESOLUTION FB_IOCTL_SET_RESOLUTION -#define FB_IOCTL_GET_BUFFER FB_IOCTL_GET_BUFFER -#define FB_IOCTL_GET_BUFFER_OFFSET FB_IOCTL_GET_BUFFER_OFFSET -#define FB_IOCTL_SET_BUFFER FB_IOCTL_SET_BUFFER -#define FB_IOCTL_FLUSH_BUFFERS FB_IOCTL_FLUSH_BUFFERS +#define FB_IOCTL_GET_PROPERTIES FB_IOCTL_GET_PROPERTIES +#define FB_IOCTL_GET_HEAD_PROPERTIES FB_IOCTL_GET_HEAD_PROPERTIES +#define FB_IOCTL_SET_HEAD_RESOLUTION FB_IOCTL_SET_HEAD_RESOLUTION +#define FB_IOCTL_SET_HEAD_VERITCAL_OFFSET_BUFFER FB_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER +#define FB_IOCTL_GET_HEAD_VERITCAL_OFFSET_BUFFER FB_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER +#define FB_IOCTL_FLUSH_HEAD_BUFFERS FB_IOCTL_FLUSH_HEAD_BUFFERS +#define FB_IOCTL_FLUSH_HEAD FB_IOCTL_FLUSH_HEAD #define KEYBOARD_IOCTL_GET_NUM_LOCK KEYBOARD_IOCTL_GET_NUM_LOCK #define KEYBOARD_IOCTL_SET_NUM_LOCK KEYBOARD_IOCTL_SET_NUM_LOCK #define KEYBOARD_IOCTL_GET_CAPS_LOCK KEYBOARD_IOCTL_GET_CAPS_LOCK diff --git a/Userland/Services/WindowServer/Screen.cpp b/Userland/Services/WindowServer/Screen.cpp index ea6bee2af5..b28fc42f40 100644 --- a/Userland/Services/WindowServer/Screen.cpp +++ b/Userland/Services/WindowServer/Screen.cpp @@ -230,8 +230,15 @@ bool Screen::open_device() return false; } - m_can_set_buffer = (fb_set_buffer(m_framebuffer_fd, 0) == 0); - m_can_device_flush_buffers = true; // If the device can't do it we revert to false + FBProperties properties; + if (fb_get_properties(m_framebuffer_fd, &properties) < 0) { + perror(String::formatted("failed to ioctl {}", info.device).characters()); + return false; + } + + m_can_device_flush_buffers = properties.partial_flushing_support; + m_can_set_buffer = properties.doublebuffer_support; + set_resolution(true); return true; } @@ -312,40 +319,49 @@ bool Screen::set_resolution(bool initial) screen_with_cursor = &ScreenInput::the().cursor_location_screen(); auto& info = screen_layout_info(); - FBResolution physical_resolution { 0, (unsigned)info.resolution.width(), (unsigned)info.resolution.height() }; - int rc = fb_set_resolution(m_framebuffer_fd, &physical_resolution); + + int rc = -1; + { + // FIXME: Add multihead support for one framebuffer + FBHeadResolution physical_resolution { 0, 0, info.resolution.width(), info.resolution.height() }; + rc = fb_set_resolution(m_framebuffer_fd, &physical_resolution); + } + dbgln_if(WSSCREEN_DEBUG, "Screen #{}: fb_set_resolution() - return code {}", index(), rc); auto on_change_resolution = [&]() { - if (initial || physical_resolution.width != (unsigned)info.resolution.width() || physical_resolution.height != (unsigned)info.resolution.height()) { + if (initial) { if (m_framebuffer) { size_t previous_size_in_bytes = m_size_in_bytes; int rc = munmap(m_framebuffer, previous_size_in_bytes); VERIFY(rc == 0); } - - int rc = fb_get_size_in_bytes(m_framebuffer_fd, &m_size_in_bytes); + FBHeadProperties properties; + properties.head_index = 0; + int rc = fb_get_head_properties(m_framebuffer_fd, &properties); VERIFY(rc == 0); + m_size_in_bytes = properties.buffer_length; m_framebuffer = (Gfx::RGBA32*)mmap(nullptr, m_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, m_framebuffer_fd, 0); VERIFY(m_framebuffer && m_framebuffer != (void*)-1); if (m_can_set_buffer) { - unsigned buffer_offset = 0; - rc = fb_get_buffer_offset(m_framebuffer_fd, 1, &buffer_offset); - if (rc == 0) { - m_back_buffer_offset = buffer_offset; - } else { - // fall back to assuming the second buffer starts right after the last line of the first - m_back_buffer_offset = physical_resolution.pitch * physical_resolution.height; - } + // Note: fall back to assuming the second buffer starts right after the last line of the first + // Note: for now, this calculation works quite well, so need to defer it to another function + // that does ioctl to figure out the correct offset. If a Framebuffer device ever happens to + // to set the second buffer at different location than this, we might need to consider bringing + // back a function with ioctl to check this. + m_back_buffer_offset = properties.pitch * properties.height; } else { m_back_buffer_offset = 0; } } - - info.resolution = { physical_resolution.width, physical_resolution.height }; - m_pitch = physical_resolution.pitch; + FBHeadProperties properties; + properties.head_index = 0; + int rc = fb_get_head_properties(m_framebuffer_fd, &properties); + VERIFY(rc == 0); + info.resolution = { properties.width, properties.height }; + m_pitch = properties.pitch; update_virtual_rect(); @@ -375,7 +391,12 @@ bool Screen::set_resolution(bool initial) void Screen::set_buffer(int index) { VERIFY(m_can_set_buffer); - int rc = fb_set_buffer(m_framebuffer_fd, index); + VERIFY(index <= 1 && index >= 0); + FBHeadVerticalOffset offset; + memset(&offset, 0, sizeof(FBHeadVerticalOffset)); + if (index == 1) + offset.offseted = 1; + int rc = fb_set_head_vertical_offset_buffer(m_framebuffer_fd, &offset); VERIFY(rc == 0); } @@ -472,6 +493,7 @@ void Screen::constrain_pending_flush_rects() fb_data.pending_flush_rects.clear_with_capacity(); for (auto const& rect : rects.rects()) { fb_data.pending_flush_rects.append({ + .head_index = 0, .x = (unsigned)rect.x(), .y = (unsigned)rect.y(), .width = (unsigned)rect.width(), @@ -511,7 +533,8 @@ void Screen::queue_flush_display_rect(Gfx::IntRect const& flush_region) return; } VERIFY(fb_data.pending_flush_rects.size() < fb_data.pending_flush_rects.capacity()); - fb_data.pending_flush_rects.append({ (unsigned)flush_region.left(), + fb_data.pending_flush_rects.append({ 0, + (unsigned)flush_region.left(), (unsigned)flush_region.top(), (unsigned)flush_region.width(), (unsigned)flush_region.height() }); @@ -555,6 +578,7 @@ void Screen::flush_display_front_buffer(int front_buffer_index, Gfx::IntRect& re VERIFY(m_can_device_flush_buffers); auto scale_factor = this->scale_factor(); FBRect flush_rect { + .head_index = 0, .x = (unsigned)(rect.x() * scale_factor), .y = (unsigned)(rect.y() * scale_factor), .width = (unsigned)(rect.width() * scale_factor), diff --git a/Userland/Services/WindowServer/ScreenLayout.ipp b/Userland/Services/WindowServer/ScreenLayout.ipp index 6703f8b9fa..04c9f4f87e 100644 --- a/Userland/Services/WindowServer/ScreenLayout.ipp +++ b/Userland/Services/WindowServer/ScreenLayout.ipp @@ -301,7 +301,9 @@ bool ScreenLayout::try_auto_add_framebuffer(String const& device_path) ScopeGuard fd_guard([&] { close(framebuffer_fd); }); - FBResolution resolution {}; + // FIXME: Add multihead support for one framebuffer + FBHeadResolution resolution {}; + memset(&resolution, 0, sizeof(FBHeadResolution)); if (fb_get_resolution(framebuffer_fd, &resolution) < 0) { int err = errno; dbgln("Error ({}) querying resolution from framebuffer device {}", err, device_path); diff --git a/Userland/Utilities/strace.cpp b/Userland/Utilities/strace.cpp index 72387f7931..6164e429de 100644 --- a/Userland/Utilities/strace.cpp +++ b/Userland/Utilities/strace.cpp @@ -137,13 +137,13 @@ HANDLE(TIOCSCTTY) HANDLE(TIOCSTI) HANDLE(TIOCNOTTY) HANDLE(TIOCSWINSZ) -HANDLE(FB_IOCTL_GET_SIZE_IN_BYTES) -HANDLE(FB_IOCTL_GET_RESOLUTION) -HANDLE(FB_IOCTL_SET_RESOLUTION) -HANDLE(FB_IOCTL_GET_BUFFER) -HANDLE(FB_IOCTL_GET_BUFFER_OFFSET) -HANDLE(FB_IOCTL_SET_BUFFER) -HANDLE(FB_IOCTL_FLUSH_BUFFERS) +HANDLE(FB_IOCTL_GET_PROPERTIES) +HANDLE(FB_IOCTL_GET_HEAD_PROPERTIES) +HANDLE(FB_IOCTL_SET_HEAD_RESOLUTION) +HANDLE(FB_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER) +HANDLE(FB_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER) +HANDLE(FB_IOCTL_FLUSH_HEAD_BUFFERS) +HANDLE(FB_IOCTL_FLUSH_HEAD) HANDLE(KEYBOARD_IOCTL_GET_NUM_LOCK) HANDLE(KEYBOARD_IOCTL_SET_NUM_LOCK) HANDLE(KEYBOARD_IOCTL_GET_CAPS_LOCK)