From eb2b0d847ea1378b487074e31bb03f58b24a990e Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Tue, 23 Nov 2021 12:49:37 +0100 Subject: [PATCH] Kernel: Allow writes larger than `PAGE_SIZE` to AC97 device Previously, `cat /dev/random > /dev/audio` would crash Serenity. Fix this by splitting up the written data into `PAGE_SIZE` chunks. --- Kernel/Devices/Audio/AC97.cpp | 23 +++++++++++++++++------ Kernel/Devices/Audio/AC97.h | 1 + 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Kernel/Devices/Audio/AC97.cpp b/Kernel/Devices/Audio/AC97.cpp index 856250d953f..f2e0302cf9b 100644 --- a/Kernel/Devices/Audio/AC97.cpp +++ b/Kernel/Devices/Audio/AC97.cpp @@ -169,8 +169,6 @@ void AC97::set_pcm_output_volume(u8 left_channel, u8 right_channel, Muted mute) ErrorOr AC97::write(OpenFileDescription&, u64, UserOrKernelBuffer const& data, size_t length) { - VERIFY(length <= PAGE_SIZE); - if (!m_output_buffer) { m_output_buffer = TRY(allocate_physical_buffer(m_output_buffer_page_count * PAGE_SIZE, "AC97 Output buffer"sv)); } @@ -179,9 +177,23 @@ ErrorOr AC97::write(OpenFileDescription&, u64, UserOrKernelBuffer const& m_buffer_descriptor_list = TRY(allocate_physical_buffer(buffer_descriptor_list_size, "AC97 Buffer Descriptor List"sv)); } - cli(); + auto remaining = length; + size_t offset = 0; + while (remaining > 0) { + TRY(write_single_buffer(data, offset, min(remaining, PAGE_SIZE))); + offset += PAGE_SIZE; + remaining -= PAGE_SIZE; + } + + return length; +} + +ErrorOr AC97::write_single_buffer(UserOrKernelBuffer const& data, size_t offset, size_t length) +{ + VERIFY(length <= PAGE_SIZE); // Block until we can write into an unused buffer + cli(); do { auto pcm_out_status = m_pcm_out_channel.reg(AC97Channel::Register::Status).in(); auto is_dma_controller_halted = (pcm_out_status & AudioStatusRegisterFlag::DMAControllerHalted) > 0; @@ -199,11 +211,10 @@ ErrorOr AC97::write(OpenFileDescription&, u64, UserOrKernelBuffer const& m_irq_queue.wait_forever("AC97"sv); } while (m_pcm_out_channel.dma_running()); - sti(); // Copy data from userspace into one of our buffers - TRY(data.read(m_output_buffer->vaddr_from_page_index(m_output_buffer_page_index).as_ptr(), length)); + TRY(data.read(m_output_buffer->vaddr_from_page_index(m_output_buffer_page_index).as_ptr(), offset, length)); if (!m_pcm_out_channel.dma_running()) { reset_pcm_out(); @@ -226,7 +237,7 @@ ErrorOr AC97::write(OpenFileDescription&, u64, UserOrKernelBuffer const& m_output_buffer_page_index = (m_output_buffer_page_index + 1) % m_output_buffer_page_count; m_buffer_descriptor_list_index = (m_buffer_descriptor_list_index + 1) % buffer_descriptor_list_max_entries; - return length; + return {}; } void AC97::AC97Channel::reset() diff --git a/Kernel/Devices/Audio/AC97.h b/Kernel/Devices/Audio/AC97.h index e6b60bf1536..db85ad5452e 100644 --- a/Kernel/Devices/Audio/AC97.h +++ b/Kernel/Devices/Audio/AC97.h @@ -152,6 +152,7 @@ private: void set_master_output_volume(u8, u8, Muted); void set_pcm_output_sample_rate(u16); void set_pcm_output_volume(u8, u8, Muted); + ErrorOr write_single_buffer(UserOrKernelBuffer const&, size_t, size_t); OwnPtr m_buffer_descriptor_list; u8 m_buffer_descriptor_list_index = 0;