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 + +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 +#include +#include +#include +#include + +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 m_dma_buffer_page; + bool m_interrupted { false }; + int m_major_version { 0 }; +}; diff --git a/Kernel/IO.h b/Kernel/IO.h index 901c7a7726..4dde65da19 100644 --- a/Kernel/IO.h +++ b/Kernel/IO.h @@ -61,4 +61,12 @@ inline void repeated_out16(u16 port, const u8* data, int data_size) : "d"(port)); } +inline void delay() +{ + // ~3 microsecs + for (auto i = 0; i < 32; i++) { + IO::in8(0x80); + } +} + } diff --git a/Kernel/Makefile b/Kernel/Makefile index ef443b1422..936fe02eb0 100644 --- a/Kernel/Makefile +++ b/Kernel/Makefile @@ -75,7 +75,8 @@ VFS_OBJS = \ FileSystem/Ext2FileSystem.o \ FileSystem/VirtualFileSystem.o \ FileSystem/FileDescription.o \ - FileSystem/SyntheticFileSystem.o + FileSystem/SyntheticFileSystem.o \ + Devices/SB16.o AK_OBJS = \ ../AK/String.o \ diff --git a/Kernel/build-root-filesystem.sh b/Kernel/build-root-filesystem.sh index b523f03a58..d373006da5 100755 --- a/Kernel/build-root-filesystem.sh +++ b/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 mnt/dev/keyboard c 85 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 ln -s /proc/self/fd/0 mnt/dev/stdin 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/SystemServer/SystemServer mnt/bin/SystemServer cp ../Servers/WindowServer/WindowServer mnt/bin/WindowServer +cp ../Servers/AudioServer/AudioServer mnt/bin/AudioServer cp ../Shell/Shell mnt/bin/Shell cp ../Libraries/LibHTML/tho mnt/bin/tho echo "done" diff --git a/Kernel/init.cpp b/Kernel/init.cpp index e3e8eb2367..b580fcfac3 100644 --- a/Kernel/init.cpp +++ b/Kernel/init.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,7 @@ VirtualConsole* tty2; VirtualConsole* tty3; KeyboardDevice* keyboard; PS2MouseDevice* ps2mouse; +SB16* sb16; DebugLogDevice* dev_debuglog; NullDevice* dev_null; SerialDevice* ttyS0; @@ -177,6 +179,7 @@ extern "C" [[noreturn]] void init() keyboard = new KeyboardDevice; ps2mouse = new PS2MouseDevice; + sb16 = new SB16; dev_null = new NullDevice; ttyS0 = new SerialDevice(SERIAL_COM1_ADDR, 64); ttyS1 = new SerialDevice(SERIAL_COM2_ADDR, 65); diff --git a/Kernel/makeall.sh b/Kernel/makeall.sh index 1440b32bde..301bdd18fe 100755 --- a/Kernel/makeall.sh +++ b/Kernel/makeall.sh @@ -21,6 +21,7 @@ build_targets="$build_targets ../Libraries/LibCore" build_targets="$build_targets ../Servers/SystemServer" build_targets="$build_targets ../Servers/LookupServer" build_targets="$build_targets ../Servers/WindowServer" +build_targets="$build_targets ../Servers/AudioServer" build_targets="$build_targets ../Libraries/LibGUI" build_targets="$build_targets ../Libraries/LibHTML" build_targets="$build_targets ../Userland" diff --git a/Kernel/run b/Kernel/run index 056a2713c7..4f2eb3928d 100755 --- a/Kernel/run +++ b/Kernel/run @@ -20,7 +20,8 @@ elif [ "$1" = "qn" ]; then -kernel kernel \ -append "${SERENITY_KERNEL_CMDLINE}" \ -hda _disk_image \ - -soundhw pcspk + -soundhw pcspk \ + -soundhw sb16 elif [ "$1" = "qtap" ]; then # ./run qtap: qemu with tap sudo $SERENITY_QEMU_BIN -s -m ${SERENITY_RAM_SIZE:-128} \ @@ -34,7 +35,8 @@ elif [ "$1" = "qtap" ]; then -kernel kernel \ -append "${SERENITY_KERNEL_CMDLINE}" \ -hda _disk_image \ - -soundhw pcspk + -soundhw pcspk \ + -soundhw sb16 elif [ "$1" = "qgrub" ]; then # ./run qgrub: qemu with grub $SERENITY_QEMU_BIN -s -m ${SERENITY_RAM_SIZE:-128} \ @@ -60,6 +62,7 @@ else -kernel kernel \ -append "${SERENITY_KERNEL_CMDLINE}" \ -hda _disk_image \ - -soundhw pcspk + -soundhw pcspk \ + -soundhw sb16 fi diff --git a/Servers/AudioServer/Makefile b/Servers/AudioServer/Makefile new file mode 100644 index 0000000000..3e36a18acd --- /dev/null +++ b/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 + diff --git a/Servers/AudioServer/main.cpp b/Servers/AudioServer/main.cpp new file mode 100644 index 0000000000..43cae47a15 --- /dev/null +++ b/Servers/AudioServer/main.cpp @@ -0,0 +1,171 @@ +#include + +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; +} diff --git a/Servers/SystemServer/main.cpp b/Servers/SystemServer/main.cpp index c413ec5cc0..6d83e73c5b 100644 --- a/Servers/SystemServer/main.cpp +++ b/Servers/SystemServer/main.cpp @@ -78,6 +78,7 @@ int main(int, char**) int highest_prio = sched_get_priority_max(SCHED_OTHER); start_process("/bin/LookupServer", lowest_prio); start_process("/bin/WindowServer", highest_prio); + start_process("/bin/AudioServer", highest_prio); start_process("/bin/Taskbar", highest_prio); start_process("/bin/Terminal", highest_prio - 1); start_process("/bin/Launcher", highest_prio);