浏览代码

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.
Robin Burchell 6 年之前
父节点
当前提交
6c4024c04a

+ 179 - 0
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;
+}

+ 40 - 0
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 };
+};

+ 8 - 0
Kernel/IO.h

@@ -61,4 +61,12 @@ inline void repeated_out16(u16 port, const u8* data, int data_size)
                  : "d"(port));
                  : "d"(port));
 }
 }
 
 
+inline void delay()
+{
+    // ~3 microsecs
+    for (auto i = 0; i < 32; i++) {
+        IO::in8(0x80);
+    }
+}
+
 }
 }

+ 2 - 1
Kernel/Makefile

@@ -75,7 +75,8 @@ VFS_OBJS = \
     FileSystem/Ext2FileSystem.o \
     FileSystem/Ext2FileSystem.o \
     FileSystem/VirtualFileSystem.o \
     FileSystem/VirtualFileSystem.o \
     FileSystem/FileDescription.o \
     FileSystem/FileDescription.o \
-    FileSystem/SyntheticFileSystem.o
+    FileSystem/SyntheticFileSystem.o \
+    Devices/SB16.o
 
 
 AK_OBJS = \
 AK_OBJS = \
     ../AK/String.o \
     ../AK/String.o \

+ 2 - 0
Kernel/build-root-filesystem.sh

@@ -38,6 +38,7 @@ mknod -m 666 mnt/dev/full c 1 7
 mknod -m 666 mnt/dev/debuglog c 1 18
 mknod -m 666 mnt/dev/debuglog c 1 18
 mknod mnt/dev/keyboard c 85 1
 mknod mnt/dev/keyboard c 85 1
 mknod mnt/dev/psaux c 10 1
 mknod mnt/dev/psaux c 10 1
+mknod -m 666 mnt/dev/audio c 42 42
 mknod -m 666 mnt/dev/ptmx c 5 2
 mknod -m 666 mnt/dev/ptmx c 5 2
 ln -s /proc/self/fd/0 mnt/dev/stdin
 ln -s /proc/self/fd/0 mnt/dev/stdin
 ln -s /proc/self/fd/1 mnt/dev/stdout
 ln -s /proc/self/fd/1 mnt/dev/stdout
@@ -87,6 +88,7 @@ cp ../Games/Snake/Snake mnt/bin/Snake
 cp ../Servers/LookupServer/LookupServer mnt/bin/LookupServer
 cp ../Servers/LookupServer/LookupServer mnt/bin/LookupServer
 cp ../Servers/SystemServer/SystemServer mnt/bin/SystemServer
 cp ../Servers/SystemServer/SystemServer mnt/bin/SystemServer
 cp ../Servers/WindowServer/WindowServer mnt/bin/WindowServer
 cp ../Servers/WindowServer/WindowServer mnt/bin/WindowServer
+cp ../Servers/AudioServer/AudioServer mnt/bin/AudioServer
 cp ../Shell/Shell mnt/bin/Shell
 cp ../Shell/Shell mnt/bin/Shell
 cp ../Libraries/LibHTML/tho mnt/bin/tho
 cp ../Libraries/LibHTML/tho mnt/bin/tho
 echo "done"
 echo "done"

+ 3 - 0
Kernel/init.cpp

@@ -16,6 +16,7 @@
 #include <Kernel/Devices/MBRPartitionTable.h>
 #include <Kernel/Devices/MBRPartitionTable.h>
 #include <Kernel/Devices/NullDevice.h>
 #include <Kernel/Devices/NullDevice.h>
 #include <Kernel/Devices/PS2MouseDevice.h>
 #include <Kernel/Devices/PS2MouseDevice.h>
+#include <Kernel/Devices/SB16.h>
 #include <Kernel/Devices/RandomDevice.h>
 #include <Kernel/Devices/RandomDevice.h>
 #include <Kernel/Devices/SerialDevice.h>
 #include <Kernel/Devices/SerialDevice.h>
 #include <Kernel/Devices/ZeroDevice.h>
 #include <Kernel/Devices/ZeroDevice.h>
@@ -39,6 +40,7 @@ VirtualConsole* tty2;
 VirtualConsole* tty3;
 VirtualConsole* tty3;
 KeyboardDevice* keyboard;
 KeyboardDevice* keyboard;
 PS2MouseDevice* ps2mouse;
 PS2MouseDevice* ps2mouse;
+SB16* sb16;
 DebugLogDevice* dev_debuglog;
 DebugLogDevice* dev_debuglog;
 NullDevice* dev_null;
 NullDevice* dev_null;
 SerialDevice* ttyS0;
 SerialDevice* ttyS0;
@@ -177,6 +179,7 @@ extern "C" [[noreturn]] void init()
 
 
     keyboard = new KeyboardDevice;
     keyboard = new KeyboardDevice;
     ps2mouse = new PS2MouseDevice;
     ps2mouse = new PS2MouseDevice;
+    sb16 = new SB16;
     dev_null = new NullDevice;
     dev_null = new NullDevice;
     ttyS0 = new SerialDevice(SERIAL_COM1_ADDR, 64);
     ttyS0 = new SerialDevice(SERIAL_COM1_ADDR, 64);
     ttyS1 = new SerialDevice(SERIAL_COM2_ADDR, 65);
     ttyS1 = new SerialDevice(SERIAL_COM2_ADDR, 65);

+ 1 - 0
Kernel/makeall.sh

@@ -21,6 +21,7 @@ build_targets="$build_targets ../Libraries/LibCore"
 build_targets="$build_targets ../Servers/SystemServer"
 build_targets="$build_targets ../Servers/SystemServer"
 build_targets="$build_targets ../Servers/LookupServer"
 build_targets="$build_targets ../Servers/LookupServer"
 build_targets="$build_targets ../Servers/WindowServer"
 build_targets="$build_targets ../Servers/WindowServer"
+build_targets="$build_targets ../Servers/AudioServer"
 build_targets="$build_targets ../Libraries/LibGUI"
 build_targets="$build_targets ../Libraries/LibGUI"
 build_targets="$build_targets ../Libraries/LibHTML"
 build_targets="$build_targets ../Libraries/LibHTML"
 build_targets="$build_targets ../Userland"
 build_targets="$build_targets ../Userland"

+ 6 - 3
Kernel/run

@@ -20,7 +20,8 @@ elif [ "$1" = "qn" ]; then
         -kernel kernel \
         -kernel kernel \
         -append "${SERENITY_KERNEL_CMDLINE}" \
         -append "${SERENITY_KERNEL_CMDLINE}" \
         -hda _disk_image \
         -hda _disk_image \
-        -soundhw pcspk
+        -soundhw pcspk \
+        -soundhw sb16
 elif [ "$1" = "qtap" ]; then
 elif [ "$1" = "qtap" ]; then
     # ./run qtap: qemu with tap
     # ./run qtap: qemu with tap
     sudo $SERENITY_QEMU_BIN -s -m ${SERENITY_RAM_SIZE:-128} \
     sudo $SERENITY_QEMU_BIN -s -m ${SERENITY_RAM_SIZE:-128} \
@@ -34,7 +35,8 @@ elif [ "$1" = "qtap" ]; then
         -kernel kernel \
         -kernel kernel \
         -append "${SERENITY_KERNEL_CMDLINE}" \
         -append "${SERENITY_KERNEL_CMDLINE}" \
         -hda _disk_image \
         -hda _disk_image \
-        -soundhw pcspk
+        -soundhw pcspk \
+        -soundhw sb16
 elif [ "$1" = "qgrub" ]; then
 elif [ "$1" = "qgrub" ]; then
     # ./run qgrub: qemu with grub
     # ./run qgrub: qemu with grub
     $SERENITY_QEMU_BIN -s -m ${SERENITY_RAM_SIZE:-128} \
     $SERENITY_QEMU_BIN -s -m ${SERENITY_RAM_SIZE:-128} \
@@ -60,6 +62,7 @@ else
         -kernel kernel \
         -kernel kernel \
         -append "${SERENITY_KERNEL_CMDLINE}" \
         -append "${SERENITY_KERNEL_CMDLINE}" \
         -hda _disk_image \
         -hda _disk_image \
-        -soundhw pcspk
+        -soundhw pcspk \
+        -soundhw sb16
 fi
 fi
 
 

+ 23 - 0
Servers/AudioServer/Makefile

@@ -0,0 +1,23 @@
+include ../../Makefile.common
+
+AUDIOSERVER_OBJS = \
+    main.o
+
+APP = AudioServer
+OBJS = $(AUDIOSERVER_OBJS)
+
+DEFINES += -DUSERLAND
+
+all: $(APP)
+
+$(APP): $(OBJS)
+	$(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lc -lcore
+
+.cpp.o:
+	@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+-include $(OBJS:%.o=%.d)
+
+clean:
+	@echo "CLEAN"; rm -f $(APP) $(OBJS) *.d
+

+ 171 - 0
Servers/AudioServer/main.cpp

@@ -0,0 +1,171 @@
+#include <LibCore/CFile.h>
+
+u32 read_u32(const ByteBuffer& buf, u32& off)
+{
+    ASSERT(buf.size() - off >= 4);
+    u32 b0 = buf[off + 0];
+    u32 b1 = buf[off + 1];
+    u32 b2 = buf[off + 2];
+    u32 b3 = buf[off + 3];
+
+    u32 ret = 0;
+    ret |= (u8(b3) << 24);
+    ret |= (u8(b2) << 16);
+    ret |= (u8(b1) << 8);
+    ret |= (u8(b0));
+
+    off += 4;
+    return ret;
+}
+
+u16 read_u16(const ByteBuffer& buf, u32& off)
+{
+    ASSERT(buf.size() - off >= 2);
+    u16 b0 = buf[off + 0];
+    u16 b1 = buf[off + 1];
+
+    u16 ret = 0;
+    ret |= (u8(b1) << 8);
+    ret |= (u8(b0));
+
+    off += 2;
+    return ret;
+}
+
+ByteBuffer read_wav_data(const StringView& path)
+{
+    CFile wav(path);
+    if (!wav.open(CIODevice::ReadOnly)) {
+        dbgprintf("Can't open wav to dump it to audio: %s", wav.error_string());
+        return {};
+    }
+
+    const auto& contents = wav.read_all();
+    u32 off = 0;
+
+    if (contents.size() - off < 12) {
+        dbgprintf("WAV is too small (no header, %d bytes)\n", contents.size());
+        return {};
+    }
+
+    dbgprintf("Trying to parse %d bytes of wav\n", contents.size());
+
+#define CHECK_OK(msg)                      \
+    do {                                   \
+        ASSERT(ok);                        \
+        if (!ok) {                         \
+            dbgprintf("%s failed\n", msg); \
+            return {};                     \
+        } else {                           \
+            dbgprintf("%S is OK!\n", msg); \
+        }                                  \
+    } while (0);
+
+    bool ok = true;
+    u32 riff = read_u32(contents, off);
+    ok = ok && riff == 0x46464952; // "RIFF"
+    CHECK_OK("RIFF header");
+
+    u32 sz = read_u32(contents, off);
+    ASSERT(sz < 1024 * 1024 * 42);
+    ok = ok && sz < 1024 * 1024 * 42; // arbitrary
+    CHECK_OK("File size");
+
+    u32 wave = read_u32(contents, off);
+    ok = ok && wave == 0x45564157; // "WAVE"
+    CHECK_OK("WAVE header");
+
+    if (contents.size() - off < 8) {
+        dbgprintf("WAV is too small (no fmt, %d bytes)\n", contents.size());
+        return {};
+    }
+
+    u32 fmt_id = read_u32(contents, off);
+    ok = ok && fmt_id == 0x20746D66; // "FMT"
+    CHECK_OK("FMT header");
+
+    u32 fmt_size = read_u32(contents, off);
+    ok = ok && fmt_size == 16;
+    ASSERT(fmt_size == 16);
+    CHECK_OK("FMT size");
+
+    if (contents.size() - off < 16) {
+        dbgprintf("WAV is too small (fmt chunk, %d bytes)\n", contents.size());
+        return {};
+    }
+
+    u16 audio_format = read_u16(contents, off);
+    ok = ok && audio_format == 1; // WAVE_FORMAT_PCM
+    ASSERT(audio_format == 1);
+    CHECK_OK("Audio format");
+
+    u16 num_channels = read_u16(contents, off);
+    ok = ok && num_channels == 1;
+    ASSERT(num_channels == 1);
+    CHECK_OK("Channel count");
+
+    u32 sample_rate = read_u32(contents, off);
+    CHECK_OK("Sample rate");
+
+    off += 4; // bytes per sec: we don't care.
+    off += 2; // block align: we don't care.
+
+    u16 bits_per_sample = read_u16(contents, off);
+    ok = ok && (bits_per_sample == 8 || bits_per_sample == 16);
+    ASSERT(bits_per_sample == 8 || bits_per_sample == 16);
+    CHECK_OK("Bits per sample");
+
+    dbgprintf("Read WAV of format %d with num_channels %d sample rate %d, bits per sample %d\n", audio_format, num_channels, sample_rate, bits_per_sample);
+
+    // Read chunks until we find DATA
+    if (off >= u32(contents.size()) - 8) {
+        ok = ok && false;
+        ASSERT_NOT_REACHED();
+        CHECK_OK("Premature EOF without DATA");
+    }
+
+    bool found_data = false;
+    u32 data_sz = 0;
+    while (off < u32(contents.size()) - 8) {
+        u32 chunk_id = read_u32(contents, off);
+        data_sz = read_u32(contents, off);
+        if (chunk_id == 0x61746164) { // DATA
+            found_data = true;
+            break;
+        }
+        off += data_sz;
+    }
+
+    ok = ok && found_data;
+    ASSERT(found_data);
+    CHECK_OK("Found no data chunk");
+
+    ok = ok && data_sz <= (contents.size() - off);
+    CHECK_OK("Bad DATA size");
+
+    return contents.slice(off, data_sz);
+}
+
+void read_and_play_wav()
+{
+    CFile audio("/dev/audio");
+    if (!audio.open(CIODevice::WriteOnly)) {
+        dbgprintf("Can't open audio device: %s", audio.error_string());
+        return;
+    }
+
+    const auto& contents = read_wav_data("/home/anon/tmp.wav");
+    const int chunk_size = 4096;
+    int i = 0;
+    while (i < contents.size()) {
+        const auto chunk = contents.slice(i, chunk_size);
+        audio.write(chunk);
+        i += chunk_size;
+    }
+}
+
+int main(int, char**)
+{
+    read_and_play_wav();
+    return 0;
+}

+ 1 - 0
Servers/SystemServer/main.cpp

@@ -78,6 +78,7 @@ int main(int, char**)
     int highest_prio = sched_get_priority_max(SCHED_OTHER);
     int highest_prio = sched_get_priority_max(SCHED_OTHER);
     start_process("/bin/LookupServer", lowest_prio);
     start_process("/bin/LookupServer", lowest_prio);
     start_process("/bin/WindowServer", highest_prio);
     start_process("/bin/WindowServer", highest_prio);
+    start_process("/bin/AudioServer", highest_prio);
     start_process("/bin/Taskbar", highest_prio);
     start_process("/bin/Taskbar", highest_prio);
     start_process("/bin/Terminal", highest_prio - 1);
     start_process("/bin/Terminal", highest_prio - 1);
     start_process("/bin/Launcher", highest_prio);
     start_process("/bin/Launcher", highest_prio);