mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-04 05:20:30 +00:00
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.
This commit is contained in:
parent
6e671f78a8
commit
6c4024c04a
Notes:
sideshowbarker
2024-07-19 13:18:41 +09:00
Author: https://github.com/rburchell Commit: https://github.com/SerenityOS/serenity/commit/6c4024c04a3 Pull-request: https://github.com/SerenityOS/serenity/pull/299 Reviewed-by: https://github.com/awesomekling ✅
11 changed files with 436 additions and 4 deletions
179
Kernel/Devices/SB16.cpp
Normal file
179
Kernel/Devices/SB16.cpp
Normal file
|
@ -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
Kernel/Devices/SB16.h
Normal file
40
Kernel/Devices/SB16.h
Normal file
|
@ -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 };
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <Kernel/Devices/MBRPartitionTable.h>
|
||||
#include <Kernel/Devices/NullDevice.h>
|
||||
#include <Kernel/Devices/PS2MouseDevice.h>
|
||||
#include <Kernel/Devices/SB16.h>
|
||||
#include <Kernel/Devices/RandomDevice.h>
|
||||
#include <Kernel/Devices/SerialDevice.h>
|
||||
#include <Kernel/Devices/ZeroDevice.h>
|
||||
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
23
Servers/AudioServer/Makefile
Normal file
23
Servers/AudioServer/Makefile
Normal file
|
@ -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
Servers/AudioServer/main.cpp
Normal file
171
Servers/AudioServer/main.cpp
Normal file
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue