1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 17:47:44 +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:
Robin Burchell 2019-07-12 19:28:01 +02:00 committed by Andreas Kling
parent 6e671f78a8
commit 6c4024c04a
11 changed files with 436 additions and 4 deletions

179
Kernel/Devices/SB16.cpp Normal file
View 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
View 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 };
};

View file

@ -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);
}
}
}

View file

@ -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 \

View file

@ -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"

View file

@ -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);

View file

@ -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"

View file

@ -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