summaryrefslogtreecommitdiff
path: root/Kernel/Devices
diff options
context:
space:
mode:
authorRobin Burchell <robin+git@viroteck.net>2019-07-12 19:28:01 +0200
committerAndreas Kling <awesomekling@gmail.com>2019-07-13 08:00:24 +0200
commit6c4024c04a354b39c89e453f281d5d67c69e22a3 (patch)
tree3a2098b7c7b29783361d1b108c3b0a1ff8221cbe /Kernel/Devices
parent6e671f78a82bc3d9785bd35307cec72a84b6dcda (diff)
downloadserenity-6c4024c04a354b39c89e453f281d5d67c69e22a3.zip
Kernel: First cut of a sb16 driver
Also add an AudioServer that (right now) doesn't do much. It tries to open, parse, and play a wav file. In the future, it can do more. My general thinking here here is that /dev/audio will be "owned" by AudioServer, and we'll do mixing in software before passing buffers off to the kernel to play, but we have to start somewhere.
Diffstat (limited to 'Kernel/Devices')
-rw-r--r--Kernel/Devices/SB16.cpp179
-rw-r--r--Kernel/Devices/SB16.h40
2 files changed, 219 insertions, 0 deletions
diff --git a/Kernel/Devices/SB16.cpp b/Kernel/Devices/SB16.cpp
new file mode 100644
index 0000000000..aad5a724e3
--- /dev/null
+++ b/Kernel/Devices/SB16.cpp
@@ -0,0 +1,179 @@
+#include "SB16.h"
+#include "IO.h"
+#include <Kernel/VM/MemoryManager.h>
+
+const u16 DSP_READ = 0x22A;
+const u16 DSP_WRITE = 0x22C;
+const u16 DSP_STATUS = 0x22E;
+const u16 DSP_R_ACK = 0x22F;
+
+/* Write a value to the DSP write register */
+void SB16::dsp_write(u8 value)
+{
+ while (IO::in8(DSP_WRITE) & 0x80) {
+ ;
+ }
+ IO::out8(DSP_WRITE, value);
+}
+
+/* Reads the value of the DSP read register */
+u8 SB16::dsp_read()
+{
+ while (!(IO::in8(DSP_STATUS) & 0x80)) {
+ ;
+ }
+ return IO::in8(DSP_READ);
+}
+
+/* Changes the sample rate of sound output */
+void SB16::set_sample_rate(uint16_t hz)
+{
+ dsp_write(0x41); // output
+ dsp_write((u8)(hz >> 8));
+ dsp_write((u8)hz);
+ dsp_write(0x42); // input
+ dsp_write((u8)(hz >> 8));
+ dsp_write((u8)hz);
+}
+
+static SB16* s_the;
+
+SB16::SB16()
+ : IRQHandler(5)
+ , CharacterDevice(42, 42) // ### ?
+{
+ s_the = this;
+ initialize();
+}
+
+SB16::~SB16()
+{
+}
+
+SB16& SB16::the()
+{
+ return *s_the;
+}
+
+void SB16::handle_irq()
+{
+ m_interrupted = true;
+
+ // Stop sound output ready for the next block.
+ dsp_write(0xd0);
+
+ IO::in8(DSP_STATUS); // 8 bit interrupt
+ if (m_major_version >= 4)
+ IO::in8(DSP_R_ACK); // 16 bit interrupt
+}
+
+void SB16::initialize()
+{
+ IO::out8(0x226, 1);
+ IO::delay();
+ IO::out8(0x226, 0);
+
+ auto data = dsp_read();
+ if (data != 0xaa) {
+ kprintf("SB16: sb not ready");
+ return;
+ }
+
+ // Get the version info
+ dsp_write(0xe1);
+ m_major_version = dsp_read();
+ auto vmin = dsp_read();
+
+ kprintf("SB16: found version %d.%d\n", m_major_version, vmin);
+ enable_irq();
+}
+
+bool SB16::can_read(FileDescription&) const
+{
+ return false;
+}
+
+ssize_t SB16::read(FileDescription&, u8*, ssize_t)
+{
+ return 0;
+}
+
+void SB16::dma_start(uint32_t length)
+{
+ const auto addr = m_dma_buffer_page->paddr().get();
+ const u8 channel = 1;
+ const u8 mode = 0;
+
+ // Disable the DMA channel
+ IO::out8(0x0a, 4 + (channel % 4));
+
+ // Clear the byte pointer flip-flop
+ IO::out8(0x0c, 0);
+
+ // Write the DMA mode for the transfer
+ IO::out8(0x0b, channel | mode);
+
+ // Write the offset of the buffer
+ IO::out8(0x02, (u8)addr);
+ IO::out8(0x02, (u8)(addr >> 8));
+
+ // Write the transfer length
+ IO::out8(0x03, (u8)(length - 1));
+ IO::out8(0x03, (u8)((length - 1) >> 8));
+
+ // Write the buffer
+ IO::out8(0x83, addr >> 16);
+
+ // Enable the DMA channel
+ IO::out8(0x0a, channel);
+}
+
+void SB16::wait_for_irq()
+{
+ m_interrupted = false;
+#ifdef SB16_DEBUG
+ kprintf("SB16: waiting for interrupt...\n");
+#endif
+ // FIXME: Add timeout.
+ while (!m_interrupted) {
+ // FIXME: Put this process into a Blocked state instead, it's stupid to wake up just to check a flag.
+ Scheduler::yield();
+ }
+#ifdef SB16_DEBUG
+ kprintf("SB16: got interrupt!\n");
+#endif
+ memory_barrier();
+}
+
+ssize_t SB16::write(FileDescription&, const u8* data, ssize_t length)
+{
+ if (!m_dma_buffer_page) {
+ kprintf("SB16: Allocating page\n");
+ m_dma_buffer_page = MM.allocate_supervisor_physical_page();
+ }
+
+ kprintf("SB16: Writing buffer of %d bytes\n", length);
+ const int BLOCK_SIZE = 32 * 1024;
+ if (length > BLOCK_SIZE) {
+ return -ENOSPC;
+ }
+ const u8 mode = 0;
+
+ disable_irq();
+ const int sample_rate = 44100;
+ set_sample_rate(sample_rate);
+ dma_start(length);
+ memcpy(m_dma_buffer_page->paddr().as_ptr(), data, length);
+
+ u8 command = 0x06;
+ command |= 0xc0;
+
+ dsp_write(command);
+ dsp_write(mode);
+ dsp_write((u8)length);
+ dsp_write((u8)(length >> 8));
+
+ enable_irq();
+ wait_for_irq();
+ return length;
+}
diff --git a/Kernel/Devices/SB16.h b/Kernel/Devices/SB16.h
new file mode 100644
index 0000000000..20bbb8f6ed
--- /dev/null
+++ b/Kernel/Devices/SB16.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <AK/CircularQueue.h>
+#include <Kernel/Devices/CharacterDevice.h>
+#include <Kernel/IRQHandler.h>
+#include <Kernel/VM/PhysicalAddress.h>
+#include <Kernel/VM/PhysicalPage.h>
+
+class SB16 final : public IRQHandler
+ , public CharacterDevice {
+public:
+ SB16();
+ virtual ~SB16() override;
+
+ static SB16& the();
+
+ // ^CharacterDevice
+ virtual bool can_read(FileDescription&) const override;
+ virtual ssize_t read(FileDescription&, u8*, ssize_t) override;
+ virtual ssize_t write(FileDescription&, const u8*, ssize_t) override;
+ virtual bool can_write(FileDescription&) const override { return true; }
+
+private:
+ // ^IRQHandler
+ virtual void handle_irq() override;
+
+ // ^CharacterDevice
+ virtual const char* class_name() const override { return "SB16"; }
+
+ void initialize();
+ void wait_for_irq();
+ void dma_start(uint32_t length);
+ void set_sample_rate(uint16_t hz);
+ void dsp_write(u8 value);
+ u8 dsp_read();
+
+ RefPtr<PhysicalPage> m_dma_buffer_page;
+ bool m_interrupted { false };
+ int m_major_version { 0 };
+};